e/serverless deployments, and monorepo architectures requiring rapid dependency resolution and unified tooling.
Core Solution
The migration workflow follows a deterministic eight-step process, preserving existing business logic while swapping the runtime, package manager, and test harness.
Step 1: Install Bun 1.2
Install Bun on your local machine or CI environment using the official installer:
curl -fsSL https://bun.sh/install | bash
Verify the installation by checking the version:
bun --version
# Should output 1.2.x
Step 2: Initialize Bun in Your Project
Navigate to your existing Node.js 21 project directory. If you don't have a bunfig.toml configuration file, create one to customize Bun's behavior:
# bunfig.toml
[install]
# Use Bun's package manager instead of npm
registry = "https://registry.npmjs.org"
[test]
# Configure test runner settings
preload = ["./test/setup.ts"]
Update your package.json to set the project type to ESM (Bun works with CommonJS, but ESM is preferred for better compatibility):
{
"type": "module",
"scripts": {
"start": "bun run src/index.ts",
"test": "bun test"
}
}
Step 3: Migrate Dependencies
Replace npm install with Bun's built-in package manager to install dependencies:
bun install
Bun will read your existing package.json and package-lock.json/yarn.lock, then generate a bun.lockb binary lockfile. If any dependencies throw errors, check for Bun-compatible alternatives or update to the latest version of the package.
Step 4: Adjust Code for Bun Compatibility
Most Node.js 21 code works in Bun out of the box, but a few adjustments may be needed:
ESM Migration
If you're using CommonJS require() syntax, switch to ESM import statements:
// Before (CommonJS)
const express = require("express");
const { PORT } = require("./config");
// After (ESM)
import express from "express";
import { PORT } from "./config.js"; // Note .js extension for ESM imports
Node-Specific APIs
Replace __dirname and __filename (not available in ESM) with Bun-compatible alternatives:
// Before (Node.js CJS)
console.log(__dirname);
// After (Bun ESM)
import { fileURLToPath } from "url";
import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Bun also supports process.env for environment variables, but you can also use the faster Bun.env alias:
const port = Bun.env.PORT || 3000;
Step 5: Update API-Specific Logic
If you're using Express for your APIs, it will work in Bun, but you can also migrate to Bun-optimized frameworks like Elysia for better performance:
// Elysia example (Bun-native)
import { Elysia } from "elysia";
const app = new Elysia()
.get("/", () => "Hello from Bun 1.2!")
.listen(3000, () => {
console.log("Server running on port 3000");
});
For ORMs like Prisma or Drizzle, update the database connection to use Bun's native drivers (e.g., bun:sqlite for SQLite, or standard PostgreSQL/MySQL drivers that work with Bun).
Step 6: Test Your Migrated APIs
Bun includes a built-in test runner compatible with Jest syntax. Update your test scripts to use bun test:
# Run all tests
bun test
# Run specific test file
bun test src/__tests__/api.test.ts
Validate API endpoints manually using curl or Postman:
curl http://localhost:3000/api/users
Run your full integration test suite to confirm no regressions.
Step 7: Optimize for Bun
Replace Node.js-specific implementations with Bun's native APIs for better performance:
- Use
Bun.serve() for HTTP servers instead of http.createServer() if not using a framework.
- Use
Bun.file() and Bun.write() for file operations instead of fs module polyfills.
- Remove unnecessary Node.js shims or polyfills (e.g.,
node-fetch is redundant since Bun has a built-in fetch API).
Step 8: Deploy to Production
Update your deployment pipeline to use Bun 1.2:
Pitfall Guide
- ESM/CJS Module Resolution Conflicts: Failing to set
"type": "module" in package.json or omitting .js extensions in relative imports breaks Bun's strict ESM resolution, causing ERR_MODULE_NOT_FOUND at runtime.
- Node Core Module Polyfill Gaps: Assuming 100% Node compatibility. While Bun covers 95%+ of Node APIs, certain native addons,
vm module features, or obscure core utilities may lack full support. Always verify against the official compatibility matrix before migration.
- Lockfile & Dependency Resolution Mismatches: Mixing
npm/yarn lockfiles with bun install can trigger duplicate resolutions, hoisting conflicts, or missing peer dependencies. Always delete old lockfiles and run bun install cleanly to generate bun.lockb.
- Skipping Staging Validation: Deploying directly to production without running the full integration suite against Bun's runtime behavior. Bun's JavaScriptCore engine handles certain edge cases (e.g.,
BigInt serialization, Date parsing) differently than V8.
- Ignoring Native API Substitutions: Continuing to use
fs polyfills or node-fetch instead of Bun.file()/Bun.write() or the built-in fetch. This leaves significant I/O performance gains on the table and increases bundle size.
- CI/CD Environment Drift: Failing to update Docker base images, GitHub Actions runners, or Kubernetes sidecars to Bun 1.2. Runtime version mismatches between development, CI, and production environments cause silent failures and inconsistent behavior.
Deliverables
- Migration Blueprint: Architectural decision matrix covering runtime selection, dependency audit workflows, ESM migration paths, and CI/CD pipeline refactoring strategies.
- Pre-Flight & Rollout Checklist: Step-by-step validation matrix including dependency compatibility verification, lockfile cleanup, staging environment smoke tests, performance benchmarking thresholds, and rollback procedures.
- Configuration Templates: Production-ready
bunfig.toml profiles, optimized Dockerfile for Alpine-based Bun deployments, GitHub Actions workflow YAML with Bun caching, and automated ESM/CJS compatibility scan scripts.