| | |

Bun 14 ⚡ Migrating Existing Node.js, Express, or Next.js Projects to Bun

⏱️ Estimated read: 10 min | |

Bun is designed as a drop-in replacement for Node.js, and most projects migrate with command substitutions rather than code rewrites . The migration is not all-or-nothing: a project can adopt bun install for speed while continuing to run under Node.js, then switch the runtime when compatibility is verified. The exam tests the command mapping, the tsconfig.json adjustments, and the known incompatibilities that require attention before the runtime swap. The critical distinction is between migrating the package manager (low risk, immediate benefit) and migrating the runtime (higher risk, requires testing). Express and Next.js have specific compatibility profiles that determine whether the runtime migration is straightforward or requires a workaround.

Key point: The safest migration path is incremental. Replace npm install with bun install first, verify the lockfile conversion, then replace node with bun for running scripts. Express works without code changes because Bun implements node:http . Next.js requires a fork or adapter for full runtime compatibility, though the package manager migration works independently .


Why migration requires a staged approach

The runtime versus package manager distinction. The package manager and the runtime are separate components. bun install produces a bun.lock and installs dependencies into node_modules. The project can continue to run under Node.js after this step. The runtime migration—using bun instead of node to execute files—is a separate decision that depends on the project’s Node.js API usage . Separating the two reduces the blast radius of a failed migration.

The Node.js API compatibility gap. Bun implements most Node.js built-in modules, but not all of them completely. The node:fs, node:path, and node:http modules are near-complete. The node:vm module is partial, and the node:test module is partial enough that Bun recommends using bun:test instead . A project that depends on these modules heavily may need to stay on Node.js or refactor the affected code.

The native module problem. Native addons compiled against Node.js’s V8 engine may not load in Bun, which uses JavaScriptCore. The N-API interface is supported, so most popular native modules (sharp, better-sqlite3, bcrypt) work. Modules that depend on V8 internals (--allow-natives-syntax, v8 heap snapshots) will not work . The migration analysis must identify the native dependencies before the runtime swap.

The TypeScript configuration problem. Bun runs TypeScript natively, but the tsconfig.json must be adjusted for Bun’s module resolution. The moduleResolution must be "bundler", allowImportingTsExtensions must be true, and the types array must include "bun" (or "bun-types" in older versions) . Without these settings, the editor reports errors that do not affect execution but make the project difficult to work with.

The trade-off. The package manager migration is low-risk and immediately beneficial: faster installs, a single lockfile, and no runtime dependency. The runtime migration is higher-risk: faster startup, native TypeScript, but potential incompatibilities with Node.js modules and native addons. The recommended sequence is package manager first, runtime second, with a rollback plan at each step .


a. The package manager migration

The first step is replacing the npm, Yarn, or pnpm lockfile with bun.lock. Bun converts existing lockfiles automatically during bun install .

# Remove old lockfiles (optional; Bun converts them automatically)
rm -f package-lock.json yarn.lock pnpm-lock.yaml

# Install with Bun
bun install

# The bun.lock is generated.
# node_modules is populated.
# The project still runs under Node.js.

The command substitutions are the next step. The package.json scripts must be updated to use Bun for the commands that are changing.

Node.js / npmBun
npm installbun install
npm install <pkg>bun add <pkg>
npm install -D <pkg>bun add -d <pkg>
npm uninstall <pkg>bun remove <pkg>
npm run <script>bun run <script> or bun <script>
node <file>bun <file>
npx <package>bunx <package>

The bunfig.toml file can be created to configure the install behavior. The frozenLockfile setting is the CI equivalent of npm ci .

# bunfig.toml
[install]
frozenLockfile = true

b. The runtime migration and tsconfig.json

The runtime migration replaces node with bun in the scripts and CI configuration. The tsconfig.json must be updated for Bun’s type resolution.

{
  "compilerOptions": {
    "lib": ["ESNext"],
    "target": "ESNext",
    "module": "Preserve",
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "allowJs": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true,
    "types": ["bun"]
  }
}

The @types/bun package provides the type definitions for Bun.serve, bun:sqlite, and other Bun-specific APIs. The bun add -d @types/bun command installs it .

The Node.js globals continue to work: process.env, Buffer, __dirname, and __filename are available. The import.meta.dir and import.meta.file properties are the ESM equivalents .


c. Express and Next.js compatibility

Express runs on Bun without code changes because Bun implements the node:http and node:https modules that Express depends on .

import express from "express";

const app = express();
const port = 8080;

app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(port, () => {
  console.log(`Listening on port ${port}...`);
});

The server is started with bun server.ts. The Express middleware ecosystem (body-parser, helmet, cors, morgan) works without changes . The performance is faster than Express on Node.js because Bun’s HTTP implementation is more efficient, though Bun.serve() is faster still for new projects that do not need Express compatibility .

Next.js is more complex. The standard Next.js does not officially support Bun as a runtime. A community fork (@openuiai/next) provides Bun compatibility with an adapter layer that handles child processes and AsyncLocalStorage . The fork’s compatibility table notes that core Next.js features work, but node:crypto has some missing methods, node:vm has edge cases, and complex IPC is not recommended . An alternative is the next-bun-compile adapter, which compiles the Next.js standalone output into a single Bun executable .

The package manager migration for Next.js works independently of the runtime migration. A Next.js project can use bun install and continue to run next dev under Node.js, gaining the install speed without the runtime compatibility risk.


Complete Example Session

# ============================================
# PART 1: BACK UP THE PROJECT
# ============================================

git checkout -b backup-before-bun
git add -A
git commit -m "Backup before Bun migration"
# ============================================
# PART 2: INSTALL BUN
# ============================================

curl -fsSL https://bun.sh/install | bash
bun --version
# Output: 1.4.x
# ============================================
# PART 3: CONVERT THE LOCKFILE
# ============================================

bun install
# bun.lock is generated.
# node_modules is populated.
# The project still runs under Node.js.
# ============================================
# PART 4: UPDATE SCRIPTS
# ============================================

# package.json
# "dev": "bun run src/index.ts"
# "build": "bun build src/index.ts --outdir dist"
# "test": "bun test"
# ============================================
# PART 5: INSTALL TYPES
# ============================================

bun add -d @types/bun
# ============================================
# PART 6: UPDATE TSCONFIG
# ============================================

# "moduleResolution": "bundler"
# "allowImportingTsExtensions": true
# "types": ["bun"]
# ============================================
# PART 7: RUN THE RUNTIME MIGRATION
# ============================================

bun run src/index.ts
# The server starts under Bun.
# ============================================
# PART 8: TEST EXPRESS
# ============================================

# If the project uses Express:
bun server.ts
# The server starts without changes.
# ============================================
# PART 9: VERIFY AND ROLLBACK PLAN
# ============================================

# Run the test suite:
bun test
# If failures occur:
git checkout backup-before-bun
rm bun.lock
npm install

The nine parts trace the migration: backup, installation, lockfile conversion, script update, type installation, tsconfig update, runtime test, Express verification, and rollback.


Quick Reference

Command Substitutions

Node.js / npmBun
npm installbun install
npm install <pkg>bun add <pkg>
npm install -D <pkg>bun add -d <pkg>
npm uninstall <pkg>bun remove <pkg>
npm run <script>bun run <script>
node <file>bun <file>
npx <package>bunx <package>

tsconfig.json Additions

SettingValue
moduleResolution"bundler"
allowImportingTsExtensionstrue
types["bun"]
module"Preserve"

Compatibility Status

ComponentStatus
node:fs, node:path, node:httpNear-complete
node:vmPartial
node:testPartial; use bun:test
ExpressFull, no changes
Next.jsFork or adapter required
Native addons (N-API)Supported
V8 internalsNot supported

Best Practices

✅ Do This:

bun install
<!-- Convert lockfile first, test before runtime swap --> # ✅
bun add -d @types/bun
<!-- Install type definitions -->                      # ✅
{ "compilerOptions": { "types": ["bun"] } }
<!-- Add Bun types to tsconfig -->                    # ✅

❌ Don’t Do This:

# Swap the runtime before testing the package manager
<!-- Migrate incrementally -->                         # ❌
# Assume all Node.js modules work
<!-- Check the compatibility table -->                # ❌
# Use node:test under Bun
<!-- Use bun:test instead -->                          # ❌

Common Pitfalls

PitfallWhy It HappensFix
Type errors on Bun globalMissing "types": ["bun"]Add to tsconfig.json
Native module failsV8 internals dependencyUse N-API alternative or stay on Node.js
node:vm behavior differsPartial implementationTest the specific use case
Next.js fails under BunNo official runtime supportUse fork or adapter, or stay on Node
Tests failJest-specific featuresMigrate to bun:test

Real-World Examples

1. Install Bun

curl -fsSL https://bun.sh/install | bash

2. Convert Lockfile

bun install

3. Add Types

bun add -d @types/bun

4. Run TypeScript

bun run src/index.ts

5. Run Express

bun server.ts

6. Update tsconfig

{ "compilerOptions": { "moduleResolution": "bundler" } }

7. Run Tests

bun test

8. CI Install

bun install --frozen-lockfile

9. Rollback

git checkout backup-before-bun && npm install

10. Check Native Modules

grep -r "node-gyp" package.json

Visual

Migration Phases

┌──────────────────────────────────────────────┐
│  PHASE 1: Package Manager                    │
│  ├── bun install (convert lockfile)          │
│  ├── Update scripts                          │
│  └── Test with Node.js runtime               │
└──────────────────┬───────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────────┐
│  PHASE 2: Runtime                            │
│  ├── bun run <file> instead of node <file>   │
│  ├── Update tsconfig.json                    │
│  ├── Install @types/bun                      │
│  └── Test with Bun runtime                   │
└──────────────────┬───────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────────┐
│  PHASE 3: CI/CD                              │
│  ├── oven-sh/setup-bun@v2                    │
│  ├── bun install --frozen-lockfile           │
│  └── bun test                                │
└──────────────────────────────────────────────┘

Express and Next.js

┌──────────────────────────────────────────────┐
│  Express                                     │
│  ├── node:http implemented                   │
│  ├── No code changes                         │
│  └── Run: bun server.ts                      │
└──────────────────┬───────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────────┐
│  Next.js                                     │
│  ├── Standard: no runtime support            │
│  ├── Fork (@openuiai/next) or adapter        │
│  └── Package manager migration works alone   │
└──────────────────────────────────────────────┘

Summary

ItemValue
Package manager migrationbun install (converts lockfile)
Runtime migrationbun <file> instead of node <file>
Type definitionsbun add -d @types/bun
tsconfig types["bun"]
tsconfig moduleResolution"bundler"
Express compatibilityFull, no changes
Next.js compatibilityFork or adapter required
node:testPartial; use bun:test
RollbackRestore old lockfile and npm install

Key takeaways:

  • Migration is incremental. The package manager can be migrated before the runtime. bun install converts the lockfile, and the project continues to run under Node.js. The runtime swap is a separate decision .
  • The command substitutions are mechanical. npm install becomes bun install, node <file> becomes bun <file>, and npx becomes bunx. The package.json scripts are updated accordingly .
  • The tsconfig.json must be adjusted. The moduleResolution must be "bundler", allowImportingTsExtensions must be true, and the types array must include "bun" for the Bun global to be recognized .
  • Express works without changes. Bun implements node:http and node:https, which are the modules Express depends on. The middleware ecosystem works identically .
  • Next.js requires a fork or adapter. The standard Next.js does not officially support Bun as a runtime. The package manager migration works independently .
  • The rollback plan is the backup branch. If the migration fails, restoring the old lockfile and running npm install returns the project to its previous state .

Remember: The migration is not a single command. It is a sequence of decisions. The package manager first, the runtime second, and the CI/CD pipeline last. The command substitutions are mechanical, but the compatibility checks are not. The tsconfig.json must be updated, the native modules must be tested, and the rollback plan must be in place before the runtime swap.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!