DEV Community

Cover image for We Moved Our API from Node to Bun. Here's What Broke (and What Got 3x Faster).
Alan West
Alan West

Posted on

We Moved Our API from Node to Bun. Here's What Broke (and What Got 3x Faster).

A 14-Second Build Dropped to 3 Seconds, Then Everything Else Went Wrong

Six months ago, our API was a standard Express + TypeScript setup running on Node.js 22. It worked fine. Builds were slow, cold starts were sluggish, and our CI pipeline took 8 minutes, but it worked. Then our infra lead said the words every team dreads: "What if we just migrated to Bun?"

Here's the full story -- the wins, the breakage, and the stuff nobody mentions in benchmark posts.

Why We Pulled the Trigger

Bun in 2026 is not the Bun of 2023. It's stable, it's battle-tested, and the numbers are hard to ignore.

Cold startup time: Bun clocks in around 5ms versus Node's 50ms. For serverless functions and container restarts, that's a meaningful difference. Our API runs on Kubernetes with autoscaling, so cold starts directly impact p99 latency during traffic spikes.

HTTP throughput: Bun's built-in HTTP server handles roughly 150,000 requests per second on simple JSON endpoints, compared to Node's 50,000. In practice, your application logic is the bottleneck, not the runtime -- but the headroom matters.

Memory usage: We measured a consistent 40% reduction in RSS memory across our services. On a cluster running 24 pods, that translated to real cost savings.

Package installation: bun install finishes in a fraction of the time. A clean install of our 400-dependency monorepo went from 45 seconds with npm to about 6 seconds with Bun. That alone saved minutes per CI run.

The Migration -- Week One

The initial port was deceptively smooth. Bun runs TypeScript natively, so we dropped our entire build step:

{
  "scripts": {
    "dev": "bun --watch src/index.ts",
    "start": "bun src/index.ts",
    "test": "bun test"
  }
}
Enter fullscreen mode Exit fullscreen mode

No tsc. No ts-node. No esbuild config. Our tsconfig.json stayed the same. Bun just read our TypeScript files and ran them.

We swapped express for Bun's native HTTP server for the main API gateway:

const server = Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);

    if (url.pathname === "/health") {
      return Response.json({ status: "ok" });
    }

    if (url.pathname.startsWith("/api/v1/")) {
      return handleApiRequest(req, url);
    }

    return new Response("Not Found", { status: 404 });
  },
});

console.log(`Server running on port ${server.port}`);
Enter fullscreen mode Exit fullscreen mode

Build time dropped from 14 seconds to 3 seconds. Dev server restarts became essentially instant. The team was thrilled. That lasted about four days.

What Broke

Native addon incompatibility. Our image processing pipeline used sharp, which depends on libvips through native Node-API bindings. At the time of our migration, sharp worked but with occasional segfaults under high concurrency. We ended up isolating image processing into a separate Node.js microservice -- not ideal, but pragmatic.

Prisma's query engine. Prisma ships a Rust-based query engine binary. It technically works with Bun, but we hit edge cases with connection pooling that didn't reproduce on Node. Queries would intermittently hang under load. We switched to Drizzle ORM, which turned out to be a net positive -- lighter, faster, and no binary dependency.

Subtle behavior differences in the crypto module. We had authentication middleware that used crypto.timingSafeEqual. The function exists in Bun, but our tests caught a difference in how Buffer arguments were validated. The fix was a two-line change, but it would have been a security-relevant bug in production if our test suite hadn't caught it.

Socket.io partial support. Our real-time notification system used Socket.io. Bun's WebSocket implementation is fast and native, but Socket.io's fallback transport mechanisms (long-polling, etc.) had issues. We rewrote the real-time layer using Bun's native WebSocket support, which was actually cleaner but took a full sprint.

Debugging tooling. Node's --inspect flag connects seamlessly to Chrome DevTools and VS Code's debugger. Bun's debugging story has improved but still feels less polished. We leaned heavily on console.log debugging during the migration, which felt like 2015.

What Got Faster

The test suite transformation was the single biggest quality-of-life improvement. Our Jest-based test suite took 90 seconds. Switching to bun test brought that down to under 5 seconds.

# Before (Jest + ts-jest)
$ npm test
# ...
# Test Suites: 47 passed, 47 total
# Time: 91.2s

# After (bun test)
$ bun test
# ...
# 47 files, 312 tests, 0 failures
# Time: 4.1s
Enter fullscreen mode Exit fullscreen mode

That's not a typo. bun test is roughly 20x faster than Jest for our suite. The difference comes from eliminating TypeScript compilation overhead and Bun's faster test runner architecture.

CI pipeline time dropped from 8 minutes to under 3 minutes. Most of that gain came from faster installs and faster tests. The actual application build was already fast, but removing the TypeScript compilation step shaved off additional seconds.

API response times improved across the board. Our p50 latency dropped from 12ms to 8ms on standard CRUD endpoints. The p99 improved more dramatically -- from 180ms to 65ms -- because Bun's garbage collection produces fewer and shorter pauses.

Six Months Later -- The Honest Assessment

The migration was worth it, but I'd be lying if I said it was painless. Here's the scorecard:

Undeniable wins: Startup speed, memory usage, install times, test runner speed, native TypeScript execution.

Conditional wins: HTTP throughput matters if you're actually CPU-bound at the runtime level. Most APIs aren't. Our throughput improvement was real but smaller than synthetic benchmarks suggested -- maybe 30-40% in practice rather than 3x.

Real costs: Two weeks of migration work for a team of four. One microservice that had to stay on Node. A complete rewrite of the WebSocket layer. Ongoing vigilance about npm package compatibility.

Still rough edges: The debugging experience isn't there yet. Some npm packages randomly break. The ecosystem of Bun-specific tooling is growing but isn't comparable to Node's 15-year head start.

Should You Migrate?

If you're starting a new project, Bun is a strong default choice in 2026. The developer experience is genuinely better, and the performance advantages are real.

If you're migrating an existing Node.js application, be strategic. Start with internal tools, staging environments, and non-critical services. Run both runtimes in parallel during the transition. And audit every native dependency before you commit -- that's where the breakage hides.

The JavaScript runtime landscape finally has real competition, and that's good for everyone. Bun pushed Node to ship better performance. Node's maturity pushed Bun to improve compatibility. Developers win either way.

But if someone tells you the migration is a weekend project, they haven't done it.

Top comments (0)