Bun 1.x in Production: Real-World Performance and Gotchas
·PkgPulse Team
TL;DR
Bun 1.x is production-ready for most Node.js workloads, but "production-ready" has caveats. The performance claims are real — Bun's HTTP server benchmarks at 3-4x Node.js, installs packages 10-25x faster, and starts up 3x quicker. The gotchas: Node.js compatibility is ~95% but that 5% breaks real packages, native addons don't work, and some edge cases in the runtime differ from Node's behavior. Verdict: use Bun for new greenfield projects; migrate existing Node.js apps only after thorough testing.
Key Takeaways
- Install speed: 10-25x faster than npm (
bun installvsnpm install) - HTTP throughput: 2-4x faster than Node.js for raw HTTP benchmarks
- Node.js compatibility: ~95% — most packages work; some native addons don't
- Built-in bundler, test runner, TS transpiler — replaces several tools
- Production gotcha: SQLite works natively; other native addons need Node.js
The Performance Claims: What Holds Up
# Install speed (real numbers on a medium project, ~200 deps):
npm install: 45 seconds (cold cache)
yarn install: 38 seconds
pnpm install: 22 seconds
bun install: 3 seconds ← 15x faster than npm
# These are real numbers. Bun's package manager is written in Zig,
# uses a global cache, and does parallel installs aggressively.
# On CI with warm cache: bun install takes ~0.5-1 second.
# HTTP server throughput (hello world JSON endpoint):
Node.js (22): ~39,000 req/s
Node.js (Fastify): ~75,000 req/s
Bun (built-in): ~120,000 req/s (3x Node.js raw, 1.6x Fastify)
Bun (Hono): ~110,000 req/s
# Startup time (a typical Express-equivalent app):
Node.js: 180ms
Bun: 55ms (3.3x faster)
# TypeScript execution (no build step needed):
# Instead of: tsc && node dist/index.js
bun run src/index.ts
# Bun transpiles TypeScript on the fly.
# No tsconfig.json required (though it respects it if present).
# Startup: same as running JavaScript.
The Built-In Toolkit (Replaces Several npm Packages)
// 1. TypeScript execution — no build step
// bun run server.ts
// Works directly. No ts-node, no tsx, no tsc first.
// 2. Built-in test runner
import { test, expect, describe } from 'bun:test';
describe('User service', () => {
test('creates user correctly', async () => {
const user = await createUser({ email: 'test@example.com' });
expect(user.email).toBe('test@example.com');
expect(user.id).toBeDefined();
});
});
// Run: bun test
// Compatible with Jest's expect API — most Jest tests work with 0 changes
// 3. Built-in bundler
// bunfig.toml or CLI:
bun build ./src/index.ts --outdir ./dist --target node
bun build ./src/index.ts --outdir ./dist --target browser --minify
// Faster than esbuild for many workloads
// 4. Built-in SQLite (no better-sqlite3 needed)
import { Database } from 'bun:sqlite';
const db = new Database('myapp.sqlite');
const users = db.query('SELECT * FROM users WHERE active = ?').all(true);
// Synchronous, fast, no native addon needed
// 5. Built-in file I/O (faster than fs)
const file = Bun.file('./data.json');
const data = await file.json();
await Bun.write('./output.json', JSON.stringify(result));
// 6. Built-in WebSocket (fast, minimal API)
Bun.serve({
port: 3000,
fetch(req, server) {
if (server.upgrade(req)) return;
return new Response('Not a WebSocket');
},
websocket: {
message(ws, msg) { ws.send(msg); },
open(ws) { console.log('connected'); },
close(ws) { console.log('disconnected'); },
},
});
Node.js Compatibility: The Real Picture
Bun's compatibility goal: "Run Node.js code without modification"
Current reality (Bun 1.x): ~95% compatible
What works:
✅ Express, Fastify, Hono, Koa
✅ Prisma, Drizzle, Mongoose
✅ React, Vue, Svelte (compiled)
✅ TypeScript (native)
✅ Most npm packages (pure JavaScript)
✅ Node.js built-in modules: fs, path, crypto, http, stream, etc.
✅ CommonJS require() and ESM import
✅ Worker threads (basic)
✅ Environment variables (process.env)
What doesn't work:
❌ Native addons (.node files compiled with node-gyp)
→ bcrypt (use bcryptjs instead)
→ sharp (use @cf-workers/image-transform or jimp)
→ canvas (no Bun equivalent yet)
→ Some database drivers (use Bun's built-in SQLite or pure-JS drivers)
⚠️ Works but differently:
→ child_process.fork() — works but interprocess is Node.js only
→ cluster module — partial support
→ Some crypto edge cases differ from Node.js
→ REPL — not identical to Node.js REPL
→ Inspector/debugger — Chrome DevTools works, some differences
Common substitutions when migrating to Bun:
# Instead of: Use:
bcrypt → bun:crypto (Argon2 built-in) or bcryptjs
sharp → jimp (pure JS, slower) or skip if edge
node-canvas → No drop-in replacement
node-gyp packages → Find pure-JS alternatives
Production Deployment Patterns
# Docker with Bun (minimal production image):
FROM oven/bun:1 AS base
WORKDIR /app
FROM base AS install
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
FROM base AS release
COPY --from=install /app/node_modules ./node_modules
COPY . .
# Bun compiles to a single executable (optional):
RUN bun build ./src/index.ts --compile --outfile server
EXPOSE 3000
CMD ["./server"] # Or: CMD ["bun", "run", "src/index.ts"]
# Size comparison:
# node:22-alpine + your app: ~180MB
# oven/bun:1-alpine + your app: ~120MB
# Bun compiled binary (--compile): ~50MB standalone executable
# Vercel (supports Bun natively since 2024):
# vercel.json:
{
"installCommand": "bun install",
"buildCommand": "bun run build"
}
# Vercel detects bun.lockb and uses Bun automatically
# Railway, Render, Fly.io: all support oven/bun Docker images
# Cloudflare Workers: NOT Bun (uses V8, not JavaScriptCore)
# AWS Lambda: use the custom runtime with oven/bun base image
Real-World Gotchas Found in Production
// Gotcha 1: process.exit() behavior differs
// In Node.js: process.exit() flushes stdout/stderr before exiting
// In Bun: may not flush in all cases
// Fix: explicit await before exit
await Bun.flush?.();
process.exit(0);
// Gotcha 2: __dirname / __filename in ESM
// Node.js ESM: __dirname is undefined (use import.meta.dirname)
// Bun: supports both __dirname AND import.meta.dirname
// Watch out when sharing code between Node.js and Bun:
const dir = typeof __dirname !== 'undefined'
? __dirname
: new URL('.', import.meta.url).pathname;
// Gotcha 3: Error stack traces
// Bun error stacks look different from Node.js
// Monitoring tools (Sentry, Datadog) may parse them differently
// Sentry's Bun SDK handles this; check your APM tool
// Gotcha 4: require() of .json files
// Node.js: const data = require('./data.json') ✅
// Bun: works, but with import assertions preferred:
import data from './data.json' with { type: 'json' };
// Gotcha 5: Bun.serve vs http.createServer
// Bun.serve is faster but doesn't have the full http.IncomingMessage API
// Express uses http.createServer internally — works fine
// Code directly using http.IncomingMessage: may need adjustment
// Gotcha 6: Long-running tests with fake timers
// Bun's test runner fake timers differ from Jest's in edge cases
// Most tests work; complex timer manipulation tests may need tweaks
Should You Migrate?
Decision framework:
New greenfield project:
→ YES — use Bun. Faster DX (install, startup, TS native).
→ No migration cost; start with Bun's defaults.
→ If you later hit a compatibility wall, switching to Node.js is easy.
Existing Node.js project (no native addons):
→ PROBABLY YES — test first, migrate if tests pass.
→ Step 1: Replace package manager only (bun install instead of npm)
→ Step 2: Run test suite with Bun (bun test or bun run jest)
→ Step 3: Run the server with Bun (bun run src/index.ts)
→ If all 3 pass: you're done, ship it.
Existing project WITH native addons (sharp, bcrypt, canvas):
→ PROBABLY NO — compatibility issues will require code changes
→ Evaluate the specific packages and find pure-JS alternatives
→ Is the migration worth the effort for your team?
High-traffic production service:
→ TEST THOROUGHLY — Bun's behavior differs in edge cases
→ Run parallel: keep Node.js in production, test Bun in staging
→ Load test with realistic traffic patterns
→ If metrics look good for 2+ weeks: migrate
The npm ecosystem consensus (2026):
→ Bun is increasingly used as a package manager even in Node.js projects
→ (bun install works for Node.js projects, just faster)
→ Full Bun runtime adoption is growing but more cautious
→ Greenfield projects: strong Bun adoption
→ Existing production apps: slower, more careful migration
Compare Bun vs Node.js download trends at PkgPulse.
See the live comparison
View bun vs. node on PkgPulse →