Skip to main content

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 install vs npm 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 →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.