# Migrate a Node.js app to Bare



This guide walks through porting a small existing Node.js app to run on [Bare](/bare/explanation/bare-runtime), standalone (no Pear involved). For the conceptual background on what's actually changing as you do this, see [Migrating from Node.js](/bare/explanation/migrating-from-nodejs).

The example app below is deliberately minimal—it writes a status file with `fs`, resolves a path with `path`, and reads values 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](/bare/reference/bare/cli) on your `PATH` to run the ported app:

```sh skip="install-instruction"
npm i -g bare
```

## Create the Node.js app [#create-the-nodejs-app]

Create the project:

```sh example=migrate-nodejs-to-bare step=setup
mkdir app
cd app
npm init -y
npm pkg set type="module"
```

Add `app/index.js`:

```javascript file=<rootDir>/examples/how-to/migrate-a-nodejs-app-to-bare/node-version/index.js title="app/index.js (Node.js)" lineNumbers {1-3} skip="example-import"
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'

const outFile = path.join(process.cwd(), process.argv[2] || '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:

```sh example=migrate-nodejs-to-bare step=run process=node-run expect="runtime: node"
node app
```

## Swap builtins for `bare-*` modules [#swap-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](/bare/explanation/migrating-from-nodejs#the-standard-library-is-opt-in-not-built-in)). Install the equivalents:

```sh example=migrate-nodejs-to-bare step=setup
cd app
npm install bare-fs bare-path bare-process
```

This installs the following dependencies:

* [`bare-fs`](/bare/reference/bare/modules/bare-fs): file system access; its API closely follows Node's `fs`.
* [`bare-path`](https://github.com/holepunchto/bare-path): path manipulation, mirroring Node's `path`.
* [`bare-process`](https://github.com/holepunchto/bare-process): process control, mirroring Node's `process`.

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()`):

```javascript file=<rootDir>/examples/how-to/migrate-a-nodejs-app-to-bare/bare-version/index.js title="app/index.js (Bare)" lineNumbers {1-3} skip="example-import"
import fs from 'bare-fs'
import path from 'bare-path'
import process from 'bare-process'

const outFile = path.join(process.cwd(), process.argv[2] || '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}`)
```

This example already imports `process` explicitly, so swapping it is as mechanical as `fs` and `path`—including the `process.argv[2]` read, which indexes identically under both runtimes (verified against Bare v1.35.0: `argv[0]` is the executable, `argv[1]` the script, `argv[2]` onward your own args, same as Node). The riskier case in a real app is code that reads `process.env` or `process.argv` as an ambient global with no import at all—that throws `ReferenceError: process is not defined` under Bare until you add one (see [Fewer globals are ambient](/bare/explanation/migrating-from-nodejs#fewer-globals-are-ambient)).

For anything beyond these three modules, the [Node.js compatibility table](/bare/reference/bare/modules#nodejs-compatibility) has the full builtin-to-`bare-*` mapping. Forgot one? See [Missing builtin Modules](/bare/how-to/troubleshooting#missing-builtin-modules-when-running-with-bare) for the error Bare throws and how to fix it.

## Run it under Bare [#run-it-under-bare]

Run the same app with the [`bare` CLI](/bare/reference/bare/cli) instead of `node`:

```sh example=migrate-nodejs-to-bare step=run process=bare-run expect="runtime: bare"
bare app
```

Same output, different runtime—that's the whole port for a script that only touches builtins.

If a larger port hangs or produces different output than the Node version, reach for the same tools you'd use in Node: `bare --inspect app` attaches the inspector, and running `bare` with no filename drops into a REPL where you can `require()` the swapped-in modules directly to narrow down where behavior diverges—see the [`bare` CLI flags](/bare/reference/bare/cli#flags).

## Handle third-party dependencies that assume Node 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`](https://github.com/holepunchto/bare-node) wrapper at install time and the unmodified `require('fs')` inside that dependency resolves to `bare-fs`:

```sh skip="install-instruction"
npm i bare-fs fs@npm:bare-node-fs
```

Each 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](/bare/how-to/troubleshooting#running-third-party-modules-written-for-nodejs) for more.

If you'd rather not track them down one at a time, [`bare-node-runtime`](https://github.com/holepunchto/bare-node-runtime) is a compatibility layer you install once that reinstates Node's globals and provides an import map for Node-targeting packages:

```sh skip="install-instruction"
npm i bare-node-runtime
```

See [Choosing a porting strategy](/bare/explanation/migrating-from-nodejs#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](/bare/how-to/troubleshooting#writing-a-module-with-support-for-bare--nodejs) in its `package.json` instead of either shim.

## Handle native addons [#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:

```sh skip="native-addon-rebuild"
# 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-napi
```

This is usually the slowest part of a real migration. If you hit `ModuleError: ADDON_NOT_FOUND`, see [Troubleshooting](/bare/how-to/troubleshooting#moduleerror-addon_not_found-cannot-find-addon-when-running-bare) for the common causes.

## Where to go from here [#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`](https://github.com/holepunchto/bare-build). See [Using Bare on its own](/bare/explanation/use-bare-standalone) for that path, or [Bundle a Bare app](/bare/how-to/run-on-native/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](/pear/explanation/workers)—that's a separate step, not covered here.

## See also [#see-also]

* [Migrating from Node.js](/bare/explanation/migrating-from-nodejs)—the conceptual differences behind each step above.
* [Node.js compatibility](/bare/reference/bare/modules#nodejs-compatibility)—the full builtin-to-`bare-*` mapping table.
* [Troubleshoot common issues](/bare/how-to/troubleshooting)—fixes for missing builtins, import maps, and `bare-pack` pitfalls.
* [`bare` CLI reference](/bare/reference/bare/cli)—flags for running scripts, the REPL, and the inspector.
* [`Bare` runtime API](/bare/reference/bare/runtime)—the `Bare` global available to your ported code.
* [`bare-fs` reference](/bare/reference/bare/modules/bare-fs)—the `fs` replacement used above.
* [Using Bare on its own](/bare/explanation/use-bare-standalone)—running the ported app standalone, including packaging as an executable.
* [Bare modules reference](/bare/reference/bare/modules)—the full `bare-*` module catalog.
