Skip to content

Bun vs Node in 2026: What I Actually Run in Production

Bun vs Node in 2026: What I Actually Run in Production

I had a side project last winter that was killing me with cold starts. Tiny Node service, deployed to a small VPS, ran in a Docker container, and every restart took about 3.8 seconds before it was answering requests. That’s nothing on a real server, but it was annoying for an internal tool I bounce in and out of all day. So one Friday I spent an evening porting it to Bun. The cold start dropped to 0.4 seconds.

I’d been hearing the Bun hype for two years and ignoring it. Now I had to take it seriously.

This post is where I’ve landed after about six months of mixing Bun and Node in real work. Short version: Bun has earned a spot in my toolbox, but it hasn’t replaced Node for the things I most care about being boring.

Where Bun actually shines for me

Three places, all of them about speed of feedback rather than raw runtime throughput.

The first is package install. bun install finishes in seconds for projects where npm install makes me get coffee. On a fresh clone of a mid-size Next.js app I have, npm took 47 seconds, pnpm took 12, and bun took 4. The numbers are not the headline. The headline is that the wait is short enough that I stop context-switching, which means I keep working.

The second is running TypeScript directly. Bun handles bun run src/index.ts with no extra config, no tsx, no ts-node, no transpile step. Here’s the before-and-after for a one-off script:

# The old Node way
npm i -D tsx typescript @types/node
npx tsx ./script.ts

# Bun
bun ./script.ts

Node has been catching up here with --experimental-strip-types, but Bun made this a non-event years earlier and the experience is still smoother for the small stuff.

The third is bundled testing. bun test gives you a Jest-flavored runner without installing Jest:

// math.test.ts
import { expect, test } from "bun:test";

test("adds", () => {
  expect(2 + 2).toBe(4);
});
bun test
# bun test v1.2.x
# math.test.ts:
# (pass) adds [0.32ms]

For tiny libraries this is wonderful. No jest config, no ts-jest, no @types/jest.

Where Node still wins, and why I keep it on the production server

A few honest reasons.

Compatibility. The npm ecosystem assumes Node. Most of the time Bun’s compatibility layer is good enough that you won’t notice. Some of the time it isn’t. I hit a real one with a Postgres driver that used a Node-specific socket option, and another with a Sentry middleware that depended on process.binding. Both got fixed eventually. Both wasted an afternoon. On a production service for a paying client I don’t want to debug “is this a Bun bug or my bug?” at 11pm.

Operational maturity. Node has fifteen years of war stories. Heap snapshots, V8 flame graphs, every PaaS supports it, AWS Lambda has a maintained runtime, Datadog has a tracer that just works, k8s autoscaling samples are all written for Node. Bun has growing support, but you’ll occasionally find yourself opening GitHub issues that nobody has ever opened before. That’s fun on a side project. It’s not fun for the on-call rotation.

Long-term API stability. Node moves slowly on purpose. Bun moves fast and breaks small things between point releases. I’ve had dependencies stop working after a bun upgrade that should have been a no-op. For something I want to redeploy in two years without touching, Node is the safer bet.

If you read those reasons and felt yourself nodding, you understand why I describe the split as: Bun for my dev loop, Node for the stuff that pays me.

Deno is the one I keep almost using

I want to like Deno. The security model is genuinely the right design. Permissions per script (--allow-net, --allow-read=/etc) is what every runtime should do.

The blocker for me has been npm interop friction. Deno added npm support in 1.28 and refined it across the 2.x line, but installing certain packages still produces strange errors with peer deps that resolve fine in Node and Bun. Last month I tried to port a small Hono app to Deno and stopped after the third “specifier not found” warning that I couldn’t quickly explain.

Deno 2.x has narrowed the gap a lot, and if I were starting a brand new app with no legacy npm deps, I’d seriously consider it. For now it’s the runtime I keep almost using and not quite picking. If you’re curious, the Deno 2 announcement post is worth a read for the npm and Node compatibility story.

A small, honest benchmark

Caveat: a tiny benchmark from one app. Take it as a flavor, not a verdict.

I ran a small Hono server doing two things: serving a static JSON config, and doing a Postgres query through a connection pool. Both runtimes used the same Hono version and the same pg driver. The host was a 2-vCPU Hetzner instance, hot pool, 30-second wrk run.

# Static JSON route, 200 connections, 30s
Node 22.11:  41,200 req/s, p99 11ms
Bun 1.2.4:   58,900 req/s, p99 8ms

# Postgres query route, 50 connections, 30s
Node 22.11:  6,800 req/s, p99 38ms
Bun 1.2.4:   7,300 req/s, p99 35ms

Two reactions. First, on the IO-bound route the gap nearly vanishes, because both runtimes are waiting on Postgres. Most real apps live in that second case. Second, the synthetic JSON test is real but rarely the bottleneck for anything I ship. Don’t migrate a service for these numbers alone.

What I run for what

My current split, today:

  • CLIs and scripts: Bun. The TypeScript-without-config plus fast install make this a one-line decision.
  • Local dev for Next.js apps: Bun for bun install, then next dev itself runs on Node because that’s what Next.js officially supports. Mixing them doesn’t break anything in practice.
  • Backend services in production: Node, deployed via Docker. I’ll revisit in a year.
  • One-off endpoints I host on Cloudflare Workers: neither, because Workers has its own runtime. Different conversation.
  • Edge functions in Vercel: V8 isolate runtime. Also a different conversation.

If you want a deeper dive on the Next.js side of this, I wrote about how I chose between the App Router and Pages Router earlier this year. Bun vs Node is a smaller decision than that one.

What’s actually changed for me

Six months ago I’d have called Bun a curiosity. Today, my dotfiles install Bun on every new machine, and I reach for it any time I’m starting fresh. What hasn’t changed is that I still write production services in Node, and I’d encourage anyone running a small business on top of a Node app to do the same for now.

The Bun team has shipped a lot. Their v1.2 announcement is a good read if you want a sense of the pace. The Node.js 22 release notes are quieter, which I think reflects the right priorities for that project.

If you do consulting work where stability matters as much as features, you may end up making the same split I did. I write more about that kind of trade-off on my work page if it’s the sort of thing you care about.

One thing to try this week

Pick one CLI script you’ve been meaning to write. A small thing that hits an API and writes a CSV, say. Write it as a single script.ts file. Run it with bun ./script.ts. Notice how much friction it removed. Then decide for yourself whether to push further.