Skip to main content

Bun vs Node.js vs Deno: Which Runtime in 2026?

·PkgPulse Team

The JavaScript runtime war has a clearer winner in 2026 than it did twelve months ago. Bun is faster. Deno 2 finally has full npm compatibility. Node.js 22 is the most improved it's been in years. But the interesting question isn't which one benchmarks best — it's which one you should actually ship to production.

We ran each runtime through the same set of benchmarks and use cases. Here's the complete breakdown.

TL;DR

New projects: Bun. It's 3-4x faster than Node.js on HTTP throughput, has an all-in-one toolkit that eliminates most toolchain decisions, and the 95% npm compatibility covers every package most applications actually need. Existing Node.js apps: stay on Node.js — the compatibility guarantee is absolute and the performance gap rarely matters in practice. TypeScript-first, security-sensitive, or Cloudflare-deploying: Deno 2 — native TypeScript with zero configuration and permissions model that's genuinely different.

Key Takeaways

  • Bun handles 52,000 req/sec vs Deno's 29,000 and Node.js's 14,000 in HTTP benchmarks
  • Bun's cold start is 8-15ms vs Deno's 40-60ms and Node.js's 60-120ms — a 10x gap that matters for serverless
  • Deno 2 runs npm packages natively — the node: compatibility layer is now good enough for real applications
  • Node.js 22 is 30% faster at startup than Node.js 20, the most meaningful improvement in years
  • Bun's package manager is 7-40x faster than npm — a 30-minute monorepo install can complete in under 5 minutes
  • Bun has 95% npm compatibility — the remaining 5% is mostly native modules and edge cases
  • Deno's permission model (explicit --allow-net, --allow-read) remains the most mature security story of the three

At a Glance

Bun 1.xNode.js 22 LTSDeno 2.x
EngineJavaScriptCore (Zig)V8V8
HTTP throughput~52,000 req/s~14,000 req/s~29,000 req/s
Cold start8–15ms60–120ms40–60ms
npm compatibility~95%100%~90%
TypeScript (native)❌ (requires config)
Package managerBuilt-in (bun)npm/yarn/pnpmBuilt-in (deno)
Bundler/test runnerBuilt-inExternalBuilt-in
Permission model
JSX support✅ Native
Web Crypto API
Deno Deploy / Bun Shell✅ Bun Shell✅ Deno Deploy

The Engine Difference

The single most important architectural fact: Node.js and Deno both run on V8 (Chrome's JavaScript engine). Bun runs on JavaScriptCore — Safari's engine — built in Zig.

JavaScriptCore has a different optimization philosophy than V8. V8 optimizes for peak throughput on long-running processes (the browser tab that's open for hours). JavaScriptCore optimizes more aggressively for startup speed and initial execution. This explains most of Bun's benchmark advantage: its 8-15ms cold start vs Node.js's 60-120ms isn't a trick — it's a consequence of using a different engine with different trade-offs.

For serverless functions where every cold start is a billable event and a latency hit, this matters enormously. For a long-running Node.js server that starts once and handles millions of requests, the startup gap is irrelevant.

HTTP Performance Benchmarks

Platform: MacBook Pro M3 Max (12 CPU), macOS 14
Test: Hello World HTTP server, 10,000 requests, 100 concurrent connections

Bun.serve():
  Throughput: 52,440 req/s
  p50 latency: 1.9ms
  p99 latency: 4.1ms

Deno.serve():
  Throughput: 29,150 req/s
  p50 latency: 3.4ms
  p99 latency: 6.2ms

Node.js http.createServer():
  Throughput: 14,320 req/s
  p50 latency: 7.0ms
  p99 latency: 11.3ms

Hono on Bun:
  Throughput: 48,900 req/s  (minimal framework overhead)

Express on Node.js:
  Throughput: 8,900 req/s   (framework overhead is significant)

Two observations worth calling out:

First, the gap is real but context-dependent. On a cloud VM with network overhead between your load balancer and application server, the actual difference in end-user response time shrinks substantially. A 14,000 req/s ceiling means your Node.js server can handle most production workloads without issues; you're unlikely to saturate it on a standard Hetzner CX32.

Second, framework choice affects the numbers more than you might expect. Hono on Bun is 3.4x faster than Express on Node — but switching to Hono on Node.js gets you most of the framework gains without switching runtimes.

Package Manager Speed

This is where Bun's advantage is least controversial:

# Cold install (no cache), typical mid-size application
bun install:    4.2 seconds
pnpm install:   18.4 seconds
npm install:    62.7 seconds
yarn install:   74.1 seconds

For a monorepo with multiple packages:

bun install:    43 seconds
npm install:    31 minutes

The mechanism: Bun uses a binary lockfile format and global content-addressed cache that links packages instead of copying them. The symlink-based approach is similar to pnpm but faster at scale.

TypeScript: Native vs Configured

One of the most concrete DX differences:

// Deno — just works, no config
deno run server.ts

// Bun — just works, no config
bun run server.ts

// Node.js — requires setup
# Option A: ts-node
npx ts-node server.ts

# Option B: node --experimental-strip-types (Node.js 22+)
node --experimental-strip-types server.ts

# Option C: compile first
npx tsc && node dist/server.js

Node.js 22 added --experimental-strip-types which removes TypeScript type annotations at runtime (doesn't check types, just strips them). It's a step in the right direction but still labeled experimental.

Bun and Deno both handle .ts files natively — you write TypeScript, you run TypeScript, no configuration required.

npm Compatibility Deep Dive

Node.js:  100% — it's the reference implementation
Bun:       ~95% — fails on some native addons (node-gyp modules)
Deno 2:    ~90% — improving rapidly with each release

What Bun's 5% gap actually means in practice:

Works perfectly: Express, Fastify, Hono, React, Next.js, tRPC, Prisma, Drizzle, Zod, most Astro, most Vite tooling, database drivers for PostgreSQL/MySQL/SQLite, Jest/Vitest, TypeScript tooling

Fails or requires workarounds: Some node-gyp compiled native modules (like sharp in some configurations), packages that inspect process.versions for very specific Node version strings, and edge cases in node:crypto API surface.

Deno 2's npm story: imports now work with npm: specifier prefix:

// Deno 2 — import npm packages directly
import express from "npm:express";
import { z } from "npm:zod";

const app = express();

The npm: prefix is required — Deno won't auto-resolve bare specifiers without it. This is a deliberate choice to preserve explicitness.

Real Code: The Same Server in All Three

// Bun — bun/server.ts
Bun.serve({
  port: 3000,
  fetch(req: Request) {
    const url = new URL(req.url);
    if (url.pathname === "/health") {
      return Response.json({ status: "ok", runtime: "bun" });
    }
    return new Response("Not Found", { status: 404 });
  },
});
console.log("Listening on http://localhost:3000");
// Deno — deno/server.ts
Deno.serve({ port: 3000 }, (req: Request) => {
  const url = new URL(req.url);
  if (url.pathname === "/health") {
    return Response.json({ status: "ok", runtime: "deno" });
  }
  return new Response("Not Found", { status: 404 });
});
console.log("Listening on http://localhost:3000");
// Node.js — node/server.ts (with --experimental-strip-types)
import { createServer } from "node:http";

const server = createServer((req, res) => {
  if (req.url === "/health") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ status: "ok", runtime: "node" }));
    return;
  }
  res.writeHead(404);
  res.end("Not Found");
});

server.listen(3000, () => console.log("Listening on http://localhost:3000"));

All three implement the Web-standard Request/Response pattern (Bun and Deno natively; Node 22 via node:http or via the new node --experimental-network-imports). The Web Fetch API compatibility means code written for Cloudflare Workers tends to run on Deno and Bun with minimal changes.

The All-in-One Toolkit Argument

Bun's biggest DX advantage is eliminating tool selection:

TaskNode.js ecosystemBun
Package managernpm/yarn/pnpm (choose one)bun install
TypeScriptts-node/tsx/esbuild (configure)Native
Test runnerJest/Vitest (install + configure)bun test
Bundlerwebpack/Vite/esbuild (choose + configure)bun build
Script runnerpackage.json scriptsbun run
.env loadingdotenv packageNative
JSXBabel/esbuild/SWC (configure)Native

Whether this is a feature or a trap depends on your context. For greenfield projects where you're making all the tool choices anyway, it's unambiguously useful. For existing projects with established tooling, it's irrelevant.

Deno's Security Model

Deno's permissions system has matured significantly and is the most meaningful differentiator it has over Bun and Node:

# Run with only the permissions your code needs
deno run \
  --allow-net=api.stripe.com,db.internal \
  --allow-read=/tmp,/app/config \
  --allow-env=DATABASE_URL,STRIPE_KEY \
  server.ts

# If the code tries to access anything else:
# PermissionDenied: Requires net access to "api.aws.com"

This matters for: running third-party scripts you don't fully trust, security-sensitive financial/healthcare applications with compliance requirements, and CI environments where supply chain attacks are a real concern.

Neither Bun nor Node.js has an equivalent model. Deno's permissions also form the foundation of Deno Deploy's isolation model — another reason to consider Deno if you're deploying to their platform.

When to Use Each

Choose Bun when:

  • Starting a new project and want maximum DX with minimum tooling decisions
  • Serverless functions where cold start cost is real money (Lambda, Cloudflare Workers)
  • Your team values package install speed in CI pipelines
  • You need native TypeScript and JSX without configuration
  • You're building CLI tools where startup time is UX

Choose Node.js when:

  • Migrating an existing application — compatibility is absolute, risk is zero
  • Using native addons compiled with node-gyp (image processing, crypto libraries)
  • Your team is deep in the npm ecosystem and already has Vite, ts-node, and Jest configured
  • You need the largest possible hiring pool of developers who know the runtime
  • Deploying to environments that only officially support Node.js (some PaaS platforms)

Choose Deno when:

  • Building TypeScript-first applications with zero configuration philosophy
  • Security requirements demand an explicit permission model
  • Deploying to Deno Deploy or Cloudflare Workers (Web-standard API compatibility)
  • Running scripts from external sources you don't fully trust
  • Teams that want import maps and URL imports instead of node_modules

Track Bun, Node.js, and Deno download trends on PkgPulse.

Related: npm vs yarn vs pnpm 2026 · Hono vs Elysia 2026 · Deno 2 vs Node.js 2026

Comments

Stay Updated

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