Bun shipped v1.0 in September 2023 with a clear pitch: replace Node, npm, Webpack, Jest, and dotenv with one binary. Two years and change later, the question isn't whether Bun is fast — every benchmark confirms that — but whether the toolchain consolidation is worth the compatibility friction for production workloads.
We spent a week running Bun 1.2.x against Node.js 22 LTS on a real Astro + Drizzle + Postgres workload, a Hono API, and a monorepo with 14 packages. Here is what holds up and what still doesn't.
What Bun Actually Bundles
Bun is four tools wearing a trench coat. The runtime is built on JavaScriptCore (Safari's engine) rather than V8, written in Zig, and ships with:
-
bun install— npm-compatible package manager that readspackage.jsonand writes a binary lockfile (bun.lock). -
bun test— Jest-compatible test runner withexpect, snapshots, and mocking built in. -
bun build— bundler with TypeScript, JSX, CSS, and tree-shaking support; targets browser, Node, or Bun. -
bun run— script runner that executes TypeScript and JSX directly, notsxorts-nodewrapper required.
The "one binary" claim is literal. Install Bun and you can delete tsx, nodemon, ts-node, vitest (or jest), esbuild, and your .env loader from devDependencies. For a fresh project that adds up to a noticeably smaller node_modules before you write a single line of code.
Bun reads
.envfiles automatically — nodotenvimport needed. It also reads.env.local,.env.production, and.env.testbased onNODE_ENV. Useful, but easy to forget when debugging "why is this var set?" on a colleague's machine.
Where Bun Wins (Performance Reality)
The performance gap shows up in three places consistently:
Package installs. On a cold cache, bun install on a typical 50-dep project finishes in roughly the time npm install spends just resolving the dependency graph. With a warm cache, Bun's content-addressable store and hard-linking puts it in pnpm's neighborhood — both are roughly an order of magnitude faster than npm. If your CI pipeline runs npm ci on every PR, switching to bun install --frozen-lockfile is the single biggest lever you can pull.
Startup time. For short-lived scripts — CLI tools, serverless cold starts, build steps — Bun's startup is measurably faster than Node. The gap closes for long-running servers, where Node's JIT eventually warms up to comparable throughput.
HTTP throughput. Bun's built-in Bun.serve outperforms Node's http module and most Node frameworks in synthetic benchmarks. The caveat is that real apps with database calls, JSON parsing, and middleware see much smaller gains — usually low double-digit percentages rather than multiples. Database drivers and your own code dominate the hot path.
The performance story isn't "Bun is faster." It's "Bun removes tooling overhead that you stopped noticing." If tsx adds 800ms to every script invocation, bun run gives that back. Multiply by a CI pipeline that runs 40 scripts and the savings compound.
The Compatibility Tax
This is where the decision gets nuanced. Bun targets Node.js API compatibility as a feature, and most pure-JavaScript packages from npm just work. The friction lives in specific places:
-
Native modules. Packages with
node-gypbindings (some database drivers, image processors, native crypto wrappers) may fail to build or run. Bun has its own native module loader and the situation has improved every release, but it's the first thing to check when migrating an existing app. -
processandclustercorner cases. Bun implements most of the NodeprocessAPI, but subtle differences inprocess.binding, internal modules, andclustersemantics break tools like PM2 and certain APM agents. -
Test framework migration.
bun testis Jest-compatible for the common case, but if your suite leans onjest.mockwith complex hoisting, custom transformers, orjest-environment-jsdom, expect a migration cost. Vitest users have an easier time — the APIs are closer. -
Workspace tooling. Bun supports workspaces, but Turbo, Nx, and Rush still default to assuming Node plus pnpm or npm. You'll spend time tuning cache keys and
packageManagerfields.
If your production runtime is AWS Lambda, Vercel Serverless Functions, or Cloudflare Workers, you don't pick the runtime — the platform does. Cloudflare Workers uses workerd (V8 isolates); Vercel and AWS Lambda use Node. Bun is most useful for local dev, CI, and self-hosted containers. Don't migrate to Bun expecting to deploy it on a platform that doesn't run it.
Should You Migrate?
The honest answer depends on what you're optimizing for.
Migrate today if:
- You run CI heavily and
npm installtime hurts. Switching the install step alone is low risk and high payoff — keep Node for runtime, use Bun for installs. - You're starting a greenfield project with no native-module dependencies and no platform constraint.
- Your scripts are mostly TypeScript with
tsxorts-node—bun runis a drop-in replacement that saves real seconds per invocation.
Wait if:
- You ship to Lambda or another Node-only platform. The dev/prod runtime divergence creates a category of bugs you don't need.
- Your stack includes pinned native dependencies (Sharp, better-sqlite3, certain ORM drivers). Verify each before migrating.
- You have a stable Node + pnpm + Vitest + tsx setup that nobody complains about. The marginal speedup may not justify the migration project.
Node.js 22 has absorbed many of Bun's headline features: built-in --watch, --env-file, native TypeScript execution (experimental in 22, stable in 24), and a built-in test runner. The developer-experience gap is smaller than it was in 2023. The raw-speed gap is still real, especially for installs and startup.
Originally published at pickuma.com. Subscribe to the RSS or follow @pickuma.bsky.social for new reviews.
Top comments (0)