Why We Run Production APIs on Bun 1.1 — 2.3× Throughput, Half the Memory, But We Keep Node for Builds
Bun 1.1 handles 18k req/s (vs 7.8k Node 20) on our tRPC API with 45MB RSS (vs 120MB Node) — but `bun build` still has ESM/CJS interop bugs and Vite 6+ requires Node. Hybrid: Bun for runtime, Node for CI/build.
Published 2026-06-17
Why We Run Production APIs on Bun 1.1 — 2.3× Throughput, Half the Memory, But We Keep Node for Builds
TL;DR: Bun 1.1 delivers 18k req/s on our tRPC API (vs 7.8k Node 20) at 45MB RSS (vs 120MB) — but bun build fails on our monorepo’s ESM/CJS boundary and Vite 6 requires Node. We run production API on Bun; CI/builds on Node 20. Full comparison →
The Context
Three-dev team, Next.js 14 (web) + tRPC/Express (API) + PostgreSQL. Node 20 LTS since 2023. API pain points: p99 180ms under load, 120MB RSS per worker (4 workers = 480MB), cold start 1.2s. Evaluated Bun 1.1 (stable May 2026) for API runtime June 2026.
What We Tested
| Runtime | Use Case | Verdict | Why |
|---|---|---|---|
| Bun 1.1 (Linux x64) | Production tRPC API (Express adapter) | ✅ | 18k req/s (wrk 10s, 100 conn), 45MB RSS, 0.3s cold start, native SQLite/Postgres |
| Node 20.18 (LTS) | Same API, cluster mode (4 workers) | ⚠️ | 7.8k req/s, 120MB RSS/worker, 1.2s cold start, mature ecosystem |
| Bun 1.1 | Next.js 14 build (bun run build) | ❌ | ESM/CJS interop fails on @trpc/next + next-auth; Vite 6 requires Node |
| Node 20.18 | Next.js build + Vite + Turborepo | ✅ | Works reliably; 2.1x slower than bun build on clean projects |
The Pivot Point
June 3: bun run start:api on staging — 18k req/s, 45MB RSS, zero ENOMEM crashes (had 2/month on Node). June 5: Tried bun run build in CI for Next.js web — Error: Cannot find module '@trpc/next' (ESM/CJS boundary in monorepo). bunx vite build — Vite 6 requires Node.js 20.19+. June 7: Hybrid approach locked in. API Dockerfile: FROM oven/bun:1.1 → bun run start:api. Web Dockerfile: FROM node:20-alpine → pnpm build. CI: bun install (3.2× faster than pnpm install) → Node for builds.
What We Use Now
Bun 1.1 for production API runtime:
- Dockerfile:
FROM oven/bun:1.1-alpine→COPY package.json bun.lockb ./→bun install --production→CMD ["bun", "run", "start:api"] - API entry:
src/api/index.ts—import { serve } from "bun"; serve({ fetch: app.fetch, port: 3001 })(Bun.serve native, no Express overhead) - Database:
bun:sqlitefor local dev,postgres(pg) for production — both native, nonode:fspolyfills - Observability:
bun --inspectfor debugging,bun:jscfor heap snapshots
Node 20 LTS for builds, CI, and web runtime:
- Dockerfile:
FROM node:20-alpine→pnpm install --frozen-lockfile→pnpm build→pnpm start - Turborepo:
turbo run build— Node required for@vercel/nft,next-swc,sharp - CI:
bun install(workspaces, 3.2×pnpm) →node .turbo/build.js(Turborepo daemon)
When You’d Choose Differently
- Node 20/22 LTS if: you need maximum ecosystem compatibility, rely on native addons (
sharp,bcrypt,canvas), or use Vite 6+ / Next.js 15+ which target Node APIs — Bun’s Node compat is ~95% but the 5% breaks builds. - Deno 2.0 if: you want secure-by-default, built-in TypeScript, JSR registry, and web-standard APIs — but slower startup than Bun, smaller ecosystem.
- Bun only if: greenfield project, no complex monorepo ESM/CJS boundaries, can use Bun-native tooling (
bun test,bun buildfor simple libs) — best DX when it works.
Tool Crucible Rating
| Dimension | Rating (1–5) | Notes |
|---|---|---|
| Overall | 4 | Runtime performance exceptional; build tooling gaps force hybrid |
| Ease of Use | 4 | Drop-in for node on API; bun install is magic; build failures are sharp edges |
| Value | 5 | Free, MIT, 2.3× throughput = 2.3× less compute cost |
| Support | 3 | Active Discord, weekly releases, but Node compat bugs depend on ecosystem |
This is part of our Runtime Evaluation series. See full comparison: Bun 1.1 vs Node 20 vs Deno 2.0: Production Runtime Benchmarks 2026
Last reviewed 2026-06-17. See our methodology and affiliate policy.