The JavaScript runtime landscape changed dramatically in the last two years. Bun hit 1.2 with significantly improved Node.js compatibility. Deno 2 launched with native npm support, killing the biggest objection to adoption. Node.js 22 LTS improved startup performance and added native TypeScript stripping.
In 2026, there are three serious options. This guide covers everything that actually matters.
TL;DR Comparison
| Node.js 22 LTS | Bun 1.2 | Deno 2 | |
|---|---|---|---|
| HTTP req/s | ~85k | ~210k | ~120k |
| Startup time | ~50ms | ~6ms | ~20ms |
| TypeScript | Strip only | Native | Native + type check |
| npm compat | ✅ Full | ✅ Full | ✅ Full (via npm:) |
| Built-in bundler | ❌ | ✅ | ✅ |
| Built-in linter/formatter | ❌ | ❌ | ✅ |
| Best for | Enterprise, legacy | Edge, scripts, monorepos | Security-first, new projects |
Node.js 22 LTS: The Safe Choice
Node.js 22 became LTS in October 2024. If you're running a large existing codebase, nothing changes for you — and that's the point.
Native TypeScript support:
# Node.js 22.6+
node --experimental-strip-types server.ts
Strips type annotations before execution. No type checking — just faster startup without a build step for simple scripts.
Improved node:test module:
import { test, describe } from 'node:test'
import assert from 'node:assert/strict'
describe('User service', () => {
test('creates user with hashed password', async () => {
const user = await createUser({ email: 'test@example.com', password: 'secret' })
assert.notEqual(user.passwordHash, 'secret')
assert.ok(user.passwordHash.startsWith('$2b$'))
})
})
Many teams are dropping Jest/Vitest for new projects. No configuration, no install.
When to use Node.js:
- Existing large codebase — lowest migration risk
- Enterprise environment — best tooling support, best hiring pool
- Libraries with native bindings — widest compatibility
Bun 1.2: The Speed Demon
Built from scratch in Zig with JavaScriptCore instead of V8. The result is significantly faster startup and I/O.
Performance numbers:
# Startup time
time bun run script.ts # ~6ms
time node script.ts # ~50ms
# Package install (large Next.js project)
bun install # ~1.2s
npm install # ~18s
pnpm install # ~8s
HTTP throughput (hello world):
- Bun: ~210k req/s
- Deno: ~120k req/s
- Node.js: ~85k req/s
Bun's built-in bundler:
await Bun.build({
entrypoints: ['./src/index.ts'],
outdir: './dist',
target: 'node',
minify: true,
})
Node.js compatibility in Bun 1.2 — the big improvement over earlier versions:
- Full
worker_threadssupport - Complete
node:cryptoimplementation -
node:child_process(spawn,exec,fork)
Most packages that work with Node.js now work with Bun. Native addons (.node files) are the main exception.
Single binary compilation:
bun build --compile ./src/cli.ts --outfile my-cli
./my-cli --help # Starts in <5ms, no Node.js required
When to use Bun:
- Lambda/Edge functions — cold start matters
- CLI tools — user sees the startup latency
- Monorepo workspace —
bun installspeed across 10+ packages - New greenfield projects
Deno 2: The Security-First Runtime
Created by Ryan Dahl (Node.js's creator) to fix the mistakes of Node. Deno 2 added npm compatibility that made Deno 1 impractical.
The security model — explicit permissions:
deno run --allow-net --allow-read=./data server.ts
// deno.json
{
"tasks": {
"start": "deno run --allow-net --allow-env=DATABASE_URL,PORT src/main.ts"
}
}
In Node.js or Bun, any dependency you install has full filesystem and network access by default. In Deno, a malicious dependency can only do what you've explicitly permitted.
npm compatibility in Deno 2:
import express from 'npm:express'
import { z } from 'npm:zod'
import Stripe from 'npm:stripe'
// JSR — the TypeScript-first registry
import { Hono } from 'jsr:@hono/hono'
No node_modules. Dependencies cached globally. First run downloads, subsequent runs are instant.
Built-in tooling (the biggest DX advantage):
deno fmt # Formatter
deno lint # Linter
deno check src/main.ts # Type checker
deno test # Test runner
deno doc # Documentation generator
deno task start # Task runner
One tool, no configuration files to maintain, no version mismatches.
TypeScript in Deno 2:
deno run --check server.ts # Full type checking
deno run server.ts # Strip only (faster)
Unlike Node's --experimental-strip-types or Bun, Deno can run full type checking without a separate tsc step.
JSR — the TypeScript-first registry:
import { Hono } from 'jsr:@hono/hono'
import { assertEquals } from 'jsr:@std/assert'
Published as TypeScript source — no compiled JS, no @types packages. Works on Deno, Node.js, and Bun.
When to use Deno 2:
- Security-sensitive applications
- New projects where tooling simplicity matters
- TypeScript-first teams
- Library authors (JSR is excellent for publishing)
Real-World Scenarios
REST API with database
// Hono — works on all three runtimes
import { Hono } from 'hono'
const app = new Hono()
app.get('/users/:id', async (c) => {
const user = await db.query.users.findFirst({
where: eq(users.id, c.req.param('id'))
})
return c.json(user)
})
// Node.js: node server.ts
// Bun: bun run server.ts
// Deno: deno run --allow-net --allow-env server.ts
All three work. Pick based on team familiarity and deployment target.
CLI tool → Bun wins
6ms vs 50ms startup is significant when the user runs your tool on every file save.
Existing Next.js app → Node.js or Bun
# Bun as drop-in — zero code changes needed
bun install
bun run dev
Most Next.js apps "just work" with Bun.
Monorepo → Bun wins on install speed
2 seconds vs 45 seconds across 15 packages adds up in CI.
TypeScript Support Comparison
| Node.js 22 | Bun 1.2 | Deno 2 | |
|---|---|---|---|
| Type stripping | ✅ | ✅ | ✅ |
| Type checking during run | ❌ | ❌ | ✅ (--check) |
| tsconfig support | Requires tsc | ✅ | ✅ |
| Paths/aliases | Requires bundler | ✅ | ✅ |
Deployment Compatibility
| Platform | Node.js | Bun | Deno |
|---|---|---|---|
| Vercel | ✅ | ✅ | ✅ |
| Railway | ✅ | ✅ | ✅ |
| Fly.io | ✅ | ✅ | ✅ |
| AWS Lambda | ✅ | ✅ (unofficial) | ❌ |
| Cloudflare Workers | ❌ | ❌ | ✅ |
| Docker | ✅ | ✅ | ✅ |
Migration Guide
Node.js → Bun (lowest friction)
curl -fsSL https://bun.sh/install | bash- Replace
npm installwithbun install - Replace
node src/index.tswithbun src/index.ts - Run your tests:
bun test(Jest-compatible API) - Check for native addon dependencies
Most projects work without code changes.
Node.js → Deno 2 (medium friction)
// deno.json
{
"imports": {
"express": "npm:express",
"zod": "npm:zod",
"@/": "./src/"
},
"tasks": {
"dev": "deno run --allow-net --allow-env --allow-read --watch src/main.ts"
}
}
Main migration work:
- Convert npm imports to
npm:specifiers - Add explicit permission flags
- Replace
__dirnamewithimport.meta.dirname
Recommendation
Staying on Node.js? Upgrade to 22 LTS, adopt
--experimental-strip-typesandnode:test. Zero migration risk.Starting something new? Try Bun first. Drop-in compatibility means low risk, speed improvements are real.
Security is first-class? Deno 2. The permissions model eliminates whole categories of supply chain risk.
Building CLI tools or Lambda functions? Bun. The startup speed advantage is the biggest practical difference.
The era of "Node.js or nothing" is over. The real risk is not choosing wrong — it's not evaluating the options at all.
Full comparison with more detail at stacknotice.com/blog/bun-deno-nodejs-comparison-2026
Top comments (0)