Node.js ruled server-side JavaScript for 15 years. Then Deno (2018) and Bun (2022) challenged the throne. Both promised to fix Node's biggest pain points — clunky TypeScript setup, security holes, and a bloated node_modules. In 2025, all three runtimes are production-ready. Here's the unbiased breakdown.
At a glance
| Node.js | Deno | Bun | |
|---|---|---|---|
| Created | 2009 (Ryan Dahl) | 2018 (Ryan Dahl) | 2022 (Jarred Sumner) |
| Engine | V8 | V8 | JavaScriptCore (WebKit) |
| Language | C++ | Rust | Zig |
| TypeScript | Manual (ts-node/tsx) | Native, built-in | Native, built-in |
| Package manager | npm / yarn / pnpm | deno add / jsr | Built-in bun install |
| npm compatibility | ✅ Native | ✅ Deno 2.x | ✅ Full |
| Security | Unrestricted by default | Permission-based (--allow-*) |
Unrestricted by default |
| Startup time | ~50–80 ms | ~30–50 ms | ~7–12 ms |
| HTTP performance | ~65k req/s | ~110k req/s | ~180k req/s |
| Stability | ✅ Battle-tested (15 yrs) | ✅ Stable (v2.x) | ⚠️ Maturing (v1.x) |
| Best for | Production APIs, ecosystem depth | Security-critical apps, scripts | Speed-first APIs, scripts, testing |
What is Node.js?
Node.js (2009) introduced JavaScript on the server using Google's V8 engine and a non-blocking I/O model. It became the world's most popular runtime, powering everything from Discord to Netflix.
Node's strengths:
- Largest ecosystem: 2M+ npm packages
- 15 years of production hardening
- Massive community and job market
- Every hosting provider supports it
Node's weaknesses:
- TypeScript requires extra setup (ts-node, tsx, or transpilation)
- CommonJS vs ESM tension (both co-exist awkwardly)
- No built-in security model — any code runs with full OS access
- Slow
npm installwith deepnode_modulestrees
// Node.js: still needs setup for TypeScript
// npm install ts-node @types/node
const http = require('http');
const server = http.createServer((req, res) => {
res.end('Hello from Node.js');
});
server.listen(3000);
What is Deno?
Deno was created by Ryan Dahl — the same person who built Node.js — as a direct response to his regrets about Node. Announced in 2018, Deno uses V8 but is written in Rust, with TypeScript built in from day one.
Deno's key innovations:
- TypeScript support out of the box (no config needed)
- Security model: code runs in a sandbox, explicit permissions required
- URL-based imports (original) → Deno 2.x added full npm compatibility
- Standard library maintained by the Deno team (JSR registry)
- Web-standard APIs:
fetch,Request,Response,URLwithout polyfills
Deno 2.x (2024) was a turning point: Full npm support, package.json support, and node_modules compatibility made Deno a viable drop-in for most Node projects.
// Deno: TypeScript runs natively, no config
// deno run --allow-net server.ts
Deno.serve({ port: 3000 }, (_req) => {
return new Response("Hello from Deno");
});
Permission flags:
| Flag | Grants access to |
|---|---|
--allow-net |
Network (can scope to specific domains) |
--allow-read |
File system reads |
--allow-write |
File system writes |
--allow-env |
Environment variables |
--allow-run |
Subprocess execution |
--allow-all (-A) |
Everything (use sparingly) |
What is Bun?
Bun (2022) is the newest entrant, built from scratch by Jarred Sumner using Zig and Apple's JavaScriptCore (JSC) engine instead of V8. The goal: be the fastest all-in-one JavaScript toolkit.
Bun ships as a single executable that replaces:
node(runtime)npm/yarn(package manager)ts-node/tsx(TypeScript runner)jest(test runner)esbuild(bundler)
// Bun: TypeScript runs natively, fastest startup
// bun run server.ts
const server = Bun.serve({
port: 3000,
fetch(req) {
return new Response("Hello from Bun");
},
});
console.log(`Running on http://localhost:${server.port}`);
Bun's key claims:
bun installis 10–25× faster thannpm install- HTTP server throughput 2–3× faster than Node.js
- Built-in test runner (Jest-compatible API)
- Built-in SQLite via
bun:sqlite - Near-complete Node.js API compatibility
Performance benchmarks
Tests run on equivalent hardware (AWS c6g.xlarge, 2025). All frameworks using minimal HTTP handler. Numbers are approximate — run your own benchmarks for your specific workload.
| Benchmark | Node.js 22 | Deno 2.x | Bun 1.x |
|---|---|---|---|
| HTTP req/s (simple) | ~65,000 | ~110,000 | ~180,000 |
| Cold start time | ~60 ms | ~40 ms | ~8 ms |
npm install (50 deps) |
~30 s | ~15 s | ~3 s |
| TypeScript execution | ~2 s (transpile) | ~0.3 s | ~0.05 s |
| SQLite read (10k rows) | ~180 ms (better-sqlite3) | ~220 ms | ~60 ms (bun:sqlite) |
| JSON parse (10 MB) | ~55 ms | ~50 ms | ~30 ms |
| Memory (idle) | ~35 MB | ~45 MB | ~25 MB |
Key takeaways:
- Bun wins on raw speed in most micro-benchmarks
- Deno leads Node.js on HTTP by ~70% (Rust I/O layer)
- For CPU-bound work, engine choice (V8 vs JSC) matters more than the runtime wrapper
- In real-world apps with database calls and I/O, differences shrink considerably
TypeScript support
| Feature | Node.js | Deno | Bun |
|---|---|---|---|
| TS support | Via ts-node / tsx / tsc | ✅ Built-in | ✅ Built-in |
| tsconfig.json | Required | Optional | Optional |
| Type checking at runtime | No (ts-node check mode) | No (uses esbuild strip) | No (strips types) |
| Decorators (Stage 3) | Via tsc | ✅ | ✅ |
| JSX/TSX | Via transform | ✅ | ✅ |
| Setup effort | High (install deps + config) | Zero | Zero |
Note: Neither Deno nor Bun perform full type-checking at runtime — they strip types for speed. Use
tsc --noEmitordeno checkfor type verification in CI.
Module system and npm compatibility
| Feature | Node.js | Deno | Bun |
|---|---|---|---|
| CommonJS | ✅ (primary) | ✅ (Deno 2.x) | ✅ |
| ESM | ✅ (.mjs or "type":"module") | ✅ (primary) | ✅ (primary) |
| npm packages | ✅ | ✅ (Deno 2.x) | ✅ |
| node_modules | ✅ | ✅ (Deno 2.x) | ✅ |
| package.json | ✅ | ✅ (Deno 2.x) | ✅ |
| URL imports | ❌ | ✅ (original style) | ❌ |
| JSR (new registry) | Via npm shim | ✅ (primary) | ✅ |
| Workspace support | ✅ (npm workspaces) | ✅ | ✅ |
Deno 1.x used URL imports like import { serve } from "https://deno.land/std/http/server.ts". Deno 2.x kept this but added full npm compatibility — you can now import express from 'npm:express' or use a package.json normally.
Built-in APIs comparison
| API | Node.js | Deno | Bun |
|---|---|---|---|
| fetch | ✅ (v18+) | ✅ | ✅ |
| WebSocket client | ✅ (ws package) | ✅ | ✅ |
| WebSocket server | ✅ (ws package) | ✅ | ✅ built-in |
| File system | fs/promises |
Deno.readFile / fs |
Bun.file() / fs |
| Test runner | node:test |
deno test |
bun test (Jest-compat) |
| Bundler | ❌ (use webpack/esbuild) | ✅ (deno bundle) |
✅ (bun build) |
| SQLite | ❌ (need better-sqlite3) | ✅ (@db/sqlite) |
✅ (bun:sqlite) |
| Password hashing | ❌ (need bcrypt) | ❌ | ✅ (Bun.password) |
| S3 / object store | ❌ | ❌ | ✅ (Bun.S3Client) |
| Watch mode | --watch (v18+) |
--watch |
--hot (HMR) |
| Environment vars | process.env |
Deno.env |
process.env / Bun.env |
Security model
| Aspect | Node.js | Deno | Bun |
|---|---|---|---|
| Default access | Full OS access | Sandboxed | Full OS access |
| Network restriction | ❌ | --allow-net=api.example.com |
❌ |
| File system restriction | ❌ | --allow-read=/tmp |
❌ |
| Environment restriction | ❌ | --allow-env=API_KEY |
❌ |
| Audit tooling | npm audit |
Built-in | bun audit |
| Supply chain attack surface | High (deep node_modules) | Lower (explicit permissions) | Medium |
| Permissions file | ❌ | deno.json "permissions" |
❌ |
Deno's permission model is ideal for running untrusted scripts, CI automation, or serverless functions where you want to enforce least-privilege at the runtime level. Node and Bun give code full system access by default — safety must be enforced at the OS/container level instead.
Ecosystem and tooling
| Category | Node.js | Deno | Bun |
|---|---|---|---|
| Package registry | npm (2M+ packages) | npm + JSR | npm + JSR |
| Package manager speed | npm (~30s) / pnpm (~8s) | deno (~15s) | bun (~3s) |
| Web framework | Express, Fastify, Hono | Hono, Oak, Fresh | Hono, ElysiaJS, Elysia |
| Test runner | Jest, Vitest | deno test |
bun test (Jest API) |
| Linter/Formatter | ESLint + Prettier | Built-in deno lint / deno fmt |
❌ (use ESLint/Prettier) |
| Bundler | esbuild, Rollup, Webpack | deno bundle / Vite |
bun build |
| ORM | Prisma, Drizzle, TypeORM | Prisma, Drizzle | Prisma, Drizzle |
| Docker image size | node:22-alpine (~60 MB) | denoland/deno (~65 MB) | oven/bun (~95 MB) |
| Hosting support | Universal | Deno Deploy, Cloudflare | Vercel, Railway, Render |
| Cloud functions | AWS Lambda, GCF, Azure | Deno Deploy (edge) | Cloudflare Workers (via adapter) |
Where Node.js wins
| Scenario | Why Node.js |
|---|---|
| Legacy codebases | Billions of existing Node projects, no migration risk |
| Maximum npm ecosystem | Some packages with native bindings only work on Node |
| Enterprise / job market | 99% of job listings and enterprises use Node |
| Hosting anywhere | Every cloud, VPS, and PaaS supports Node |
| Large teams | Deepest documentation, tutorials, StackOverflow answers |
| Mature framework ecosystem | Express, NestJS, Fastify — battle-tested for years |
| Native modules (N-API) | C++ addons like bcrypt, sharp work natively |
| Risk aversion | Oldest, most predictable behavior |
Where Deno wins
| Scenario | Why Deno |
|---|---|
| Security-critical scripts | Fine-grained permissions prevent supply chain damage |
| TypeScript-first projects | Zero-config TS, no build step for small scripts |
| CLI tools | deno compile makes a single self-contained binary |
| Edge computing | Deno Deploy is the most mature edge runtime |
| URL-based dependencies | Import directly from CDN for quick scripts |
| Linting/formatting built-in | deno lint and deno fmt eliminate config overhead |
| Web-standard APIs | Closest to browser APIs — code ports between environments |
| Running untrusted code | Sandbox model protects the host system |
Where Bun wins
| Scenario | Why Bun |
|---|---|
| Raw HTTP throughput | 2–3× faster than Node for high-concurrency APIs |
| Package install speed | 10–25× faster than npm, critical for CI/CD |
| TypeScript scripts | Sub-50ms startup makes it ideal for CLI tools |
| Monorepo local dev | Faster installs and test runner speed up inner loop |
| Test runner replacement | Jest-compatible API with faster execution |
| SQLite-heavy workloads | bun:sqlite outperforms better-sqlite3 |
| Minimal API servers | ElysiaJS + Bun delivers top-tier req/s numbers |
| Rapid prototyping | One binary, zero config, immediate TypeScript |
Full comparison mega-table
| Feature | Node.js 22 | Deno 2.x | Bun 1.x |
|---|---|---|---|
| Engine | V8 | V8 | JavaScriptCore |
| Written in | C++ | Rust | Zig |
| Version (2025) | 22.x LTS | 2.x | 1.x |
| TypeScript | Manual | Built-in | Built-in |
| ESM support | ✅ (awkward CJS/ESM mix) | ✅ (primary) | ✅ (primary) |
| npm packages | ✅ | ✅ | ✅ |
| Security sandbox | ❌ | ✅ | ❌ |
| Startup time | ~60 ms | ~40 ms | ~8 ms |
| HTTP perf | ~65k req/s | ~110k req/s | ~180k req/s |
| Package install | ~30 s | ~15 s | ~3 s |
| Built-in test runner | ✅ (node:test) | ✅ (deno test) | ✅ (bun test) |
| Built-in bundler | ❌ | ✅ | ✅ |
| Built-in linter | ❌ | ✅ | ❌ |
| Built-in formatter | ❌ | ✅ | ❌ |
| Built-in SQLite | ❌ | ✅ | ✅ |
| Single binary compile | ❌ (use pkg/nexe) | ✅ | ✅ |
| Watch mode | ✅ | ✅ | ✅ (with HMR) |
| Web APIs (fetch, etc.) | ✅ (v18+) | ✅ | ✅ |
| Stability | ✅✅✅ | ✅✅ | ✅ |
| Ecosystem size | ✅✅✅ | ✅✅ | ✅✅ |
Migration paths
Node.js → Bun
Most Node.js code runs on Bun without changes. Start with:
# Replace node with bun
bun run server.js # instead of: node server.js
bun run index.ts # TypeScript works natively
# Replace npm
bun install # instead of: npm install
bun add express # instead of: npm install express
bun run build # same script names from package.json
Common gotchas:
- Some native addons (N-API modules) may not work yet
__dirname/__filenamework but useimport.meta.diridiomatically- Some Node.js built-in behaviors differ slightly (check Bun compatibility page)
Node.js → Deno 2.x
# Install Deno
curl -fsSL https://deno.land/install.sh | sh
# Run existing Node project (Deno 2 reads package.json)
deno run --allow-all server.ts
# Or install deps and run
deno install # reads package.json, installs to node_modules
deno run server.ts # then run with appropriate --allow-* flags
Common gotchas:
- Need to add
--allow-*flags (or--allow-alltemporarily) - Some npm packages with native bindings may not work
- Prefer
deno.jsonoverpackage.jsonfor new Deno projects
Decision guide
Do you need maximum ecosystem compatibility?
→ Node.js
Do you run scripts that might include untrusted code?
→ Deno (permission model protects you)
Is install speed or test speed a bottleneck in CI?
→ Bun
Are you building a brand-new TypeScript API from scratch?
→ Bun (speed) or Deno (security + built-in tools)
Are you maintaining an existing Node.js app?
→ Stay on Node, or add Bun as a drop-in runner (low risk)
Do you need to deploy to edge computing?
→ Deno Deploy (most mature) or Cloudflare Workers
Common mistakes
| Mistake | Why it's a problem | Fix |
|---|---|---|
| Assuming Bun = 100% Node.js compatible | Some native N-API modules break | Test before switching; check bun.sh/compatibility |
Using --allow-all in Deno everywhere |
Defeats the security model | Use fine-grained flags like --allow-net=api.example.com |
| Benchmarking hello-world and extrapolating | Micro-benchmark ≠ real-world | Benchmark your actual database + I/O stack |
| Using Bun in production before testing stability | Bun 1.x is maturing; edge cases exist | Pin Bun version; run full test suite before deploying |
| Migrating large teams to Deno for speed | Deno ecosystem is smaller than Node's | Measure real bottlenecks first; Node is plenty fast for most APIs |
| Ignoring Deno for CLI tools | Deno compile makes distributable binaries | Deno is excellent for TypeScript CLI tools |
| Installing Node.js toolchain alongside Bun | Defeats the purpose | Use bun as a full Node replacement for new projects |
Not using bun test when already on Bun |
Missing 2–5× faster test runs vs Jest | Replace Jest with bun test — API is compatible |
Node.js vs Deno vs Bun vs other runtimes
| Runtime | Engine | Best for | Maturity | npm compat |
|---|---|---|---|---|
| Node.js | V8 | Production APIs, enterprise | ✅✅✅ | ✅ (native) |
| Deno | V8 | Scripts, edge, security | ✅✅ | ✅ (v2+) |
| Bun | JSC | Speed-critical, developer tooling | ✅ | ✅ |
| Cloudflare Workers | V8 (isolates) | Edge CDN functions | ✅✅ | Partial |
| WinterJS | SpiderMonkey | WASI/embedded | Beta | Partial |
| LLRT (AWS) | QuickJS | Lambda cold starts | Experimental | Partial |
Frequently asked questions
Should I switch my production Node.js app to Bun? Not without testing. Bun is compatible with most Node.js code, but subtle differences in built-in module behavior and unimplemented N-API modules can cause silent bugs. Run your full test suite on Bun first. For new projects, Bun is a safe bet. For legacy production apps, stick with Node unless you have a specific performance problem.
Is Deno 2.x finally compatible with npm packages?
Yes — Deno 2.x added full npm compatibility including package.json, node_modules, and npm: specifiers. You can run most Node.js applications on Deno 2.x with minimal changes. The main remaining gaps are packages with native bindings.
Why does Bun use JavaScriptCore instead of V8? JSC (WebKit's engine, used in Safari) has faster startup times and often better single-threaded performance for certain workloads. V8 (Chrome/Node/Deno) has more aggressive JIT optimizations and a larger optimization community. For Bun's use case (fast scripts, tooling), JSC's startup advantage matters more.
Can I use Deno for serious production backends? Absolutely. Deno Deploy (edge), Deno's HTTP server, and its npm compatibility make it production-capable. Companies like Netlify use Deno internally. The ecosystem is smaller than Node's but growing, and Deno 2.x closed most of the compatibility gaps.
Which has the best TypeScript developer experience?
Deno and Bun both run TypeScript natively without configuration. Deno additionally ships a built-in linter (deno lint) and formatter (deno fmt), making it the most zero-config TypeScript environment. Bun is faster but requires external linting/formatting tools. Node requires the most setup but has the richest IDE tooling ecosystem.
Will Bun or Deno replace Node.js? Unlikely in the near term. Node.js has 15 years of ecosystem momentum, universal hosting support, and massive adoption. What's more likely: Bun becomes the default package manager and test runner for many Node.js projects (already happening), and Deno captures the edge computing and scripting niches. Node's core will continue improving — Node 22 added native TypeScript stripping, narrowing the gap.