Migrate a Node.js app to Bare
Port an existing Node.js app to run on Bare: swap builtins for bare-* modules, handle native addons, and verify the port runs identically under both runtimes.
This guide walks through porting a small existing Node.js app to run on Bare, standalone (no Pear involved). For the conceptual background on what's actually changing as you do this, see Migrating from Node.js.
The example app below is deliberately minimal—it writes a status file with fs, resolves a path with path, and reads a value off process—so the port is easy to follow end to end. The same steps apply to a larger app; you'll just be touching more files.
You'll need the bare CLI on your PATH to run the ported app:
npm i -g bareCreate the Node.js app
Create the project:
mkdir app
cd app
npm init -y
npm pkg set type="module"Add app/index.js:
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
const outFile = path.join(process.cwd(), 'status.json')
const status = { runtime: 'node' }
fs.writeFileSync(outFile, JSON.stringify(status))
const saved = JSON.parse(fs.readFileSync(outFile, 'utf8'))
console.log(`wrote status to ${outFile}`)
console.log(`runtime: ${saved.runtime}`)Run it with node to confirm the starting point works:
node appSwap builtins for bare-* modules
Bare doesn't bundle fs, path, or process—each is an installable module you add explicitly (see The standard library is opt-in, not built in). Install the equivalents:
cd app
npm install bare-fs bare-path bare-processThis installs the following dependencies:
bare-fs: file system access; its API closely follows Node'sfs.bare-path: path manipulation, mirroring Node'spath.bare-process: process control, mirroring Node'sprocess.
Then change the three import lines. Nothing else in the file needs to change—these three mirror the Node APIs this script uses (writeFileSync, readFileSync, join, and cwd()):
import fs from 'bare-fs'
import path from 'bare-path'
import process from 'bare-process'
const outFile = path.join(process.cwd(), 'status.json')
const status = { runtime: 'bare' }
fs.writeFileSync(outFile, JSON.stringify(status))
const saved = JSON.parse(fs.readFileSync(outFile, 'utf8'))
console.log(`wrote status to ${outFile}`)
console.log(`runtime: ${saved.runtime}`)For anything beyond these three modules, the Node.js compatibility table has the full builtin-to-bare-* mapping.
Run it under Bare
Run the same app with the bare CLI instead of node:
bare appSame output, different runtime—that's the whole port for a script that only touches builtins.
Handle third-party dependencies that assume Node builtins
Real apps usually have dependencies that themselves require('fs') or similar, and you can't edit their source. Alias the builtin name to a bare-node wrapper at install time and the unmodified require('fs') inside that dependency resolves to bare-fs:
npm i bare-fs fs@npm:bare-node-fsEach wrapper is a one-line re-export, so npm only ever includes bare-fs once no matter how many dependencies need it. Repeat per builtin your dependency tree touches—see Running third-party modules written for Node.js for more.
If you'd rather not track them down one at a time, bare-node-runtime is a compatibility layer you install once that reinstates Node's globals and provides an import map for Node-targeting packages:
npm i bare-node-runtimeSee Choosing a porting strategy for the tradeoffs. If you're the one maintaining a dependency that must run on both runtimes, use an import map in its package.json instead of either shim.
Handle native addons
If the app or a dependency has a native addon (a .node file, or a binding.gyp in its source), it won't load under Bare unmodified—Node-API addons target Node's ABI, not Bare's:
# Option 1: rebuild the addon against Bare's native-addon API
# (see the addon's own build instructions—this varies per addon)
# Option 2: build against Node-API compatibility headers instead
npm install bare-compat-napiThis is usually the slowest part of a real migration. If you hit AddonError: ADDON_NOT_FOUND, see Troubleshooting for the common causes.
Where to go from here
A Bare app can be compiled into a single standalone executable—no Node.js, Bare, or anything else required on the machine that runs it—with bare-build. See Using Bare on its own for that path, or Bundle a Bare app to package it for embedding in a native app.
If you're instead migrating code toward a peer-to-peer Pear app, the Bare app you just ported is the starting point for a worker—that's a separate step, not covered here.
See also
- Migrating from Node.js—the conceptual differences behind each step above.
- Node.js compatibility—the full builtin-to-
bare-*mapping table. - Troubleshoot common issues—fixes for missing builtins, import maps, and
bare-packpitfalls. bareCLI reference—flags for running scripts, the REPL, and the inspector.Bareruntime API—theBareglobal available to your ported code.bare-fsreference—thefsreplacement used above.- Using Bare on its own—running the ported app standalone, including packaging as an executable.
- Bare modules reference—the full
bare-*module catalog.