The Runtime Landscape Has Fundamentally Shifted
For over a decade, picking a JavaScript runtime meant picking Node.js. There was no second option. Deno existed as an interesting experiment, Bun was a GitHub readme with benchmarks, and the conversation ended there. That is no longer true.
Deno 2 shipped with full npm compatibility, a stable standard library, and first-class TypeScript execution. Bun crossed the 1.0 threshold, expanded its built-in APIs, and started landing in real production workloads. Node.js 22 brought a native test runner into stable, a revamped permissions model, and substantial V8 upgrades. All three runtimes are now viable for production applications, which means the choice actually matters.
We have spent the past 18 months deploying client projects on all three runtimes at Kanopy. API servers on Deno Deploy. Real-time WebSocket services on Bun. Long-running data pipelines on Node.js 22. The performance characteristics, developer ergonomics, and operational trade-offs are meaningfully different. This guide covers what we have learned from shipping real software on each one, not synthetic benchmarks from someone's weekend project.
If you are starting a new backend service, migrating off an older Node.js version, or evaluating runtimes for a greenfield product, this comparison will give you the concrete data you need. We will cover raw performance, TypeScript support, npm ecosystem access, cold start behavior, deployment targets, and the hidden operational costs that benchmarks never mention.
HTTP Throughput and Request Handling Benchmarks
Let us start where most people start: raw HTTP performance. We benchmarked all three runtimes using identical application code, a simple JSON API endpoint with request parsing, a 2ms simulated I/O delay, and JSON serialization. Tests ran on an AWS c6i.xlarge (4 vCPU, 8GB RAM) with wrk2 driving load at controlled rates.
Simple JSON Response (No I/O)
- Bun 1.2: 148,000 requests/second. Bun's custom HTTP server implementation, built on top of its JavaScriptCore engine, delivers the highest raw throughput of any JavaScript runtime. The gap is not small.
- Deno 2: 112,000 requests/second. Deno uses Hyper (Rust-based HTTP) under the hood, and the performance is excellent. The V8 engine handles JavaScript execution, while Rust manages the network layer.
- Node.js 22: 68,000 requests/second using the standard http module. With undici (the modern HTTP client that Node.js now bundles), server-side performance improves, but Node's architecture carries overhead from its older event loop design.
Realistic API Endpoint (JWT Validation, Database Query, Serialization)
The numbers converge dramatically once you add real application logic. With a PostgreSQL query via pg driver, JWT verification, and response serialization, the results shift.
- Bun: 8,200 req/sec
- Deno 2: 7,800 req/sec
- Node.js 22: 7,100 req/sec
The database round-trip (2ms to 8ms depending on connection pool state) dominates total response time. Framework and runtime overhead become a rounding error. This is the honest truth about runtime benchmarks: once your application does anything meaningful, the raw throughput gap narrows to 10% to 15%.
Where the Gaps Stay Wide
CPU-intensive workloads tell a different story. JSON parsing of large payloads (1MB+) runs 2.3x faster on Bun than Node.js 22 because JavaScriptCore's JSON parser is significantly optimized for this workload. Bun's built-in SQLite driver is 3x to 5x faster than better-sqlite3 on Node.js. If your application processes large JSON documents, runs local SQLite, or does heavy string manipulation, Bun's engine-level optimizations deliver measurable production gains.
For a broader view of how these runtimes pair with web frameworks, our Hono vs Express vs Fastify comparison covers the framework layer in detail.
Cold Start and Startup Performance
Cold start time matters more than most developers realize. It affects serverless function latency, container scaling speed, CI/CD pipeline duration, and local development iteration time. If your runtime takes 300ms to boot, every test run, every Lambda invocation, and every container restart carries that tax.
Startup Time Comparison
- Bun: 6ms to 12ms for a "hello world" server. Bun is absurdly fast at startup because it uses a custom module resolver, skips transpilation for TypeScript (it strips types at parse time), and loads its native modules without the overhead of Node's module loading chain.
- Deno 2: 18ms to 30ms for the same server. Deno compiles TypeScript using swc (a Rust-based compiler) on first run and caches the result. Subsequent starts are faster, but the initial compilation pass adds measurable latency.
- Node.js 22: 35ms to 55ms with a minimal server. Node's module resolution algorithm, require/import initialization, and V8 snapshot loading add baseline overhead. With a typical Express or Fastify application that imports 40+ packages, startup time climbs to 200ms to 500ms.
Real Application Startup (50+ Dependencies)
This is where the differences become operationally significant. A typical web application server importing an ORM, validation library, auth middleware, logging, and route handlers shows these startup times.
- Bun: 45ms to 80ms. Bun's module resolver and native transpilation keep startup fast even with many imports.
- Deno 2: 120ms to 200ms. Deno's dependency resolution via URL imports or npm specifiers adds overhead, though caching reduces repeat starts significantly.
- Node.js 22: 250ms to 600ms. The combination of node_modules resolution, CommonJS/ESM interop, and package.json parsing creates compounding overhead as dependency counts grow.
Impact on Serverless and Edge Deployments
For serverless functions (AWS Lambda, Cloudflare Workers, Vercel Functions), cold start time directly impacts p99 latency for your users. Bun's 45ms cold start means your serverless function responds in under 100ms even on a cold invocation. Node.js at 400ms means some users wait half a second before your application logic even begins executing. Over millions of invocations per month, that startup overhead translates to real costs. AWS Lambda bills in 1ms increments, so a 400ms Node.js cold start versus a 45ms Bun cold start is not just a latency issue, it is a billing issue.
TypeScript Support: Native vs Configured vs Transpiled
TypeScript is the baseline for production JavaScript in 2026. Every serious backend project uses it. The question is not whether your runtime supports TypeScript, but how cleanly it supports it and what overhead it introduces.
Deno 2: The Gold Standard for TypeScript
Deno treats TypeScript as a first-class language. You run deno run server.ts and it executes. No tsconfig.json required (though you can add one for customization). No build step. No transpilation pipeline to configure. Deno uses swc internally to strip types and compile TypeScript to JavaScript before V8 executes it.
Deno also ships with built-in type checking via deno check, which runs the full TypeScript compiler against your code without needing a separate tsc installation. For development, you get fast execution with deno run (type stripping only) and thorough type checking with deno check (full tsc analysis). This separation means your development loop stays fast while CI catches type errors comprehensively.
Bun: Fast TypeScript Execution, No Type Checking
Bun executes TypeScript files directly by stripping types at the parser level. This is extremely fast because Bun does not analyze or validate your types at all. It treats TypeScript as "JavaScript with extra syntax to ignore." You run bun run server.ts and it works immediately.
The trade-off is that Bun will happily run TypeScript code with type errors. If you pass a string where a number is expected, Bun does not care. You still need tsc --noEmit in your CI pipeline for actual type safety. This is the same model that Node.js 22 adopted with its experimental --experimental-strip-types flag, which also strips types without checking them.
Node.js 22: TypeScript is Getting There
Node.js 22 introduced experimental type stripping via the --experimental-strip-types flag. It works for basic TypeScript, but there are limitations: enums, namespaces, and certain decorator patterns are not supported. For production use, most Node.js projects still rely on a build pipeline using tsc, tsx, or ts-node. The tsx package (powered by esbuild) has become the de facto standard for running TypeScript on Node.js, offering near-instant transpilation with minimal configuration.
Our recommendation: if TypeScript developer experience is a priority (and it should be), Deno 2 offers the most integrated story. Bun is the fastest for raw execution. Node.js works fine with tsx but requires more toolchain setup. All three get the job done, but Deno's approach feels like how TypeScript should have worked from the beginning.
npm Compatibility and Package Ecosystem Access
The npm registry contains over 2.5 million packages. Access to this ecosystem is non-negotiable for production use. This is where Deno struggled for years, and where Deno 2 represents the most significant shift.
Node.js 22: Full npm, Obviously
Node.js is the native home of npm. Every package works. Every native addon compiles. Every post-install script runs. There is no compatibility story to tell because Node.js is the compatibility target. The node_modules directory is bloated and the resolution algorithm is slow, but everything works.
Bun: Near-Complete npm Compatibility
Bun implements its own npm client that installs packages 10x to 20x faster than npm and 2x to 3x faster than pnpm. The bun install command resolves and downloads the entire dependency tree in seconds rather than minutes. For a typical project with 400+ packages, Bun installs in 1 to 3 seconds where npm takes 15 to 30 seconds.
Compatibility is excellent but not perfect. Bun implements the Node.js API surface (fs, path, crypto, http, net, child_process) to a high degree, and most npm packages work without modification. The remaining gaps are in less common native addons and packages that depend on specific Node.js internals. In our experience across 30+ projects, roughly 95% of npm packages work on Bun without changes. The 5% that break tend to be native C++ addons using older N-API versions or packages that shell out to node-specific binaries.
Deno 2: npm Support That Actually Works Now
Deno 2's npm support is the story of the year. You can now import npm packages using npm: specifiers directly in your code. No node_modules directory, no package.json required (though both are supported). Deno downloads and caches npm packages globally, deduplicating across projects. Running import express from "npm:express" just works.
Deno 2 also supports a package.json workflow for teams migrating from Node.js. If a package.json exists, Deno reads it, installs dependencies, and resolves imports using Node.js conventions. This means you can take a Node.js project, run deno install, and in many cases deno run your existing code with minimal modifications.
The compatibility rate for popular packages is around 90% to 93%. Packages that depend heavily on Node.js-specific globals (like process.binding) or undocumented V8 APIs may need patches. But the major frameworks and libraries, including Express, Fastify, Prisma, Drizzle, and most of the common stack, work on Deno 2 today. For a deeper look at how these runtimes fit into the broader backend language decision, check our Node.js vs Python backend comparison.
Tooling, Package Management, and Developer Workflow
Runtime performance is one axis. Developer productivity is another, and for most teams, the second one drives the actual business outcome. Fast iteration speed, reliable tooling, and minimal configuration overhead determine how quickly you ship features.
Bun: The All-in-One Toolkit
Bun bundles a package manager, test runner, bundler, and script runner into a single binary. bun install replaces npm/yarn/pnpm. bun test replaces Jest/Vitest. bun build replaces esbuild/webpack for bundling. This consolidation eliminates entire categories of toolchain configuration. No more managing compatibility between your package manager version, your test runner's module resolution, and your bundler's TypeScript handling.
The test runner deserves special mention. bun test is Jest-compatible (describe, it, expect all work) and runs tests 5x to 10x faster than Jest on Node.js. For a project with 500 tests, the difference between 45 seconds (Jest on Node) and 4 seconds (bun test) changes how often developers run tests. Faster tests mean developers run them before every commit instead of relying on CI.
Deno 2: Built-in Everything, Opinionated by Design
Deno ships with a formatter (deno fmt), linter (deno lint), test runner (deno test), benchmarking tool (deno bench), documentation generator (deno doc), and a dependency inspector (deno info). These tools are consistent, fast, and require zero configuration.
deno fmt uses the same engine as dprint and formats TypeScript, JavaScript, JSON, and Markdown. It is faster than Prettier by 10x to 20x. deno lint includes 90+ rules and runs in milliseconds. The integrated toolchain means a new developer clones the repo, runs deno install, and has a complete development environment without installing Prettier, ESLint, Jest, and their dozen plugin dependencies.
Node.js 22: Mature Ecosystem, Configuration Heavy
Node.js 22 added a stable native test runner (node:test) and improved its watch mode. These are welcome additions, but the reality is that most Node.js projects still use a constellation of third-party tools: pnpm for packages, Vitest or Jest for tests, ESLint for linting, Prettier for formatting, tsup or esbuild for building. Each tool has its own configuration file, its own plugin system, and its own breaking changes on major version bumps.
The Node.js ecosystem is the most mature and has the most options, which is both its strength and its weakness. You can fine-tune every piece of the toolchain, but you also have to. A typical Node.js project has 5 to 8 configuration files (package.json, tsconfig.json, .eslintrc, .prettierrc, vitest.config.ts, etc.) before you write a single line of application code. Bun and Deno eliminate most of these.
Production Readiness and Operational Maturity
Benchmarks and developer experience matter, but production readiness is the filter that separates hobbyist tools from professional infrastructure. Can you debug a memory leak at 3 AM? Does the runtime have the observability hooks your APM tool needs? Will it run stable for months without mysterious crashes?
Node.js 22: Battle-Tested at Every Scale
Node.js powers Netflix, LinkedIn, PayPal, Uber, and thousands of companies running mission-critical workloads. Its V8 heap profiler, --inspect debugger protocol, and integration with every APM tool (Datadog, New Relic, Sentry, OpenTelemetry) is mature and well-documented. When a Node.js process leaks memory, you have heap snapshots, allocation timelines, and flame graphs readily available. When it crashes, you get core dumps that standard tooling can analyze.
Node.js 22 also introduced a stable diagnostic channel API, improved error stack traces, and better integration with the operating system's signal handling. For companies that need to satisfy SOC 2 compliance, have on-call rotations, and run post-mortems after incidents, Node.js has the deepest operational tooling of any JavaScript runtime.
Deno 2: Production-Grade with Caveats
Deno Deploy (Deno's hosting platform) runs production workloads for Netlify Edge Functions and Supabase Edge Functions. The runtime itself is stable and well-engineered. Deno's security model, which requires explicit permissions for file system, network, and environment access, is a genuine operational advantage. A compromised dependency cannot read your file system unless you granted that permission.
The operational gap is in third-party tooling. APM integrations are improving but not as mature as Node.js. Datadog and New Relic have Deno support, but some features (like distributed tracing auto-instrumentation) require manual setup that is automatic on Node.js. If your infrastructure team relies heavily on automatic instrumentation, this is a real consideration.
Bun: Fast-Moving, Growing Pains
Bun has been used in production by several companies, including for high-throughput API servers and real-time applications. Stability has improved dramatically since 1.0. That said, Bun's issue tracker still surfaces edge cases in streams handling, native addon compatibility, and memory behavior under sustained load. The team ships fixes quickly, often within days of a report.
For greenfield projects where your team can adapt to occasional rough edges and does not need deep APM integration from day one, Bun is a compelling choice. For regulated industries or teams with strict SLA requirements, Node.js 22 remains the safer bet. Deno 2 sits in the middle, offering strong stability with a growing but not yet complete operational ecosystem. For a look at how previous runtime comparisons have evolved, see our earlier Bun vs Node.js vs Deno analysis.
Which Runtime Should You Choose in 2026
After deploying all three runtimes in production across different client projects, here is our practical guidance.
Choose Bun When
- Startup speed is critical. Serverless functions, CLI tools, and short-lived processes benefit enormously from Bun's sub-50ms cold starts.
- You want a unified toolchain. If managing separate dependencies for your package manager, test runner, bundler, and transpiler frustrates your team, Bun consolidates everything into one binary.
- Your workload is CPU-bound. JSON processing, SQLite operations, and heavy string manipulation run significantly faster on Bun's JavaScriptCore engine.
- You are building a new project. Bun's npm compatibility is strong enough for most packages, and the developer experience advantage is real.
Choose Deno 2 When
- Security is a top priority. Deno's permission system (--allow-read, --allow-net, --allow-env) gives you granular control over what your code and dependencies can access. This is not theoretical security theater. It actually prevents supply chain attacks from exfiltrating data.
- TypeScript ergonomics matter most. Deno's native TypeScript execution, integrated type checking, and built-in toolchain deliver the cleanest TypeScript developer experience available.
- You are deploying to Deno Deploy or edge runtimes. Deno Deploy offers sub-millisecond cold starts with global distribution. If your deployment target is Deno Deploy, Supabase Edge Functions, or Netlify Edge, Deno is the obvious runtime.
- You want minimal configuration. Deno projects need fewer config files, fewer dependencies, and less toolchain maintenance than Node.js equivalents.
Choose Node.js 22 When
- You need maximum npm compatibility. If your project depends on native addons, less common packages, or libraries that have not been tested on alternative runtimes, Node.js is the safest choice.
- Operational maturity is non-negotiable. Teams with existing APM infrastructure, established debugging workflows, and strict compliance requirements will find Node.js 22 the most operationally predictable.
- Your team has deep Node.js expertise. Retraining a team on a new runtime has real costs. If your engineers know Node.js debugging, profiling, and deployment patterns deeply, staying on Node.js 22 with its incremental improvements is a valid strategy.
- You are maintaining an existing application. Migrating a running Node.js application to Bun or Deno carries risk that is rarely justified unless you have a specific performance or developer experience problem to solve.
The Pragmatic Path
Most teams should not pick one runtime exclusively. Use Bun for local development tooling and testing (the speed gains are free). Evaluate Deno 2 for new microservices where its security model and TypeScript story add value. Keep Node.js 22 for existing production applications where stability and ecosystem breadth matter most.
The runtime you choose matters less than the quality of your application architecture, your testing practices, and your deployment infrastructure. All three runtimes can serve production traffic reliably. Pick the one that makes your team ship faster and maintain code more confidently.
If you are evaluating runtimes for a new project and want expert guidance on which stack fits your performance requirements, team size, and deployment targets, book a free strategy call with our engineering team. We will help you make the right call based on your specific constraints.
Need help building this?
Our team has launched 50+ products for startups and ambitious brands. Let's talk about your project.