Architecture · October 28, 2026 · Daniel Murillo
Node.js vs Bun vs Deno in 2026: an architecture decision
The best runtime is the one that meets your requirements with the right balance of performance, compatibility, security, and operational cost.
The best runtime is the one that meets your application’s requirements with the right balance of performance, compatibility, security, complexity, and operational cost.
Introduction: three runtimes, different priorities
Node.js, Bun, and Deno run JavaScript, but they do not pursue exactly the same proposal.
- Node.js bets on a broad ecosystem, compatibility, and a mature operational platform.
- Bun combines a JavaScriptCore-based runtime with integrated tools and aims for high performance across multiple scenarios.
- Deno emphasizes explicit permissions, web standards, and integrated tools, while expanding compatibility with Node and npm.
The comparison becomes confusing when you simply ask which is “the best.” One runtime can be excellent for scripts and CLIs, another can reduce risk for an enterprise platform, and another can fit better when permissions and web APIs are priorities.
The decision should not be a popularity contest or a table sorted by requests per second. It should start from the load, the dependencies, and the operational capabilities the project needs.
1. Executive summary
| Dimension | Node.js | Bun | Deno |
|---|---|---|---|
| Engine | V8 | JavaScriptCore | V8 |
| Priority | Compatibility and ecosystem | Performance and integrated tools | Security, web standards, and tooling |
| TypeScript | Built-in support for common syntax in recent versions; separate type-check | Built-in execution/transpilation; separate type-check | Built-in execution and deno check |
| Packages | npm, pnpm, Yarn | bun install and npm packages | npm, JSR, and lockfiles from several managers |
| Node APIs | Primary reference | Broad compatibility, not total | Growing compatibility, not total |
| I/O permissions | Usually per process user and OS | Not the central differentiator | Explicit permissions for files, network, environment, and processes |
| Representative frameworks | Express, Fastify, NestJS, AdonisJS, Hono | Elysia, Hono; also many compatible Node apps | Hono, Fresh, Oak; many npm dependencies with validation |
| Typical strength | Mature dependencies and known operations | Startup, tooling, and certain performance workloads | Permission model and integrated experience |
| Main caution | Fragmented tooling and need to govern dependencies | Compatibility and observability differences | Concrete dependency compatibility and operational permissions |
The table is an initial guide, not an absolute ranking. Capabilities evolve with each version.
2. Do not mix the layers
To compare rigorously, separate four layers:
- JavaScript engine: V8 or JavaScriptCore.
- Runtime: networking, files, processes, modules, timers, and system tools.
- Framework: routing, middleware, validation, dependency injection, and conventions.
- Application: business logic, database queries, authentication, cache, logging, and third-party calls.
A Bun.serve() benchmark is not a fair comparison against a NestJS application. Nor does an Express-with-TLS test let you conclude which runtime will be better in a service that spends most of its time waiting on PostgreSQL.
The comparison must state which layer it is measuring.
3. Published benchmarks: useful evidence, not a final verdict
Project benchmarks help identify hypotheses. They should not be mixed as if they all came from the same machine and methodology.
Express over HTTPS
Bun publishes an Express 5.2.1 test with TLS 1.3, bombardier, Linux x64, and AMD EPYC. The data are medians of three runs:
| Runtime | Version | Requests/s | Peak RAM |
|---|---|---|---|
| Bun | 1.4 | 48 243 | 105 MB |
| Node.js | 26.7.0 | 25 181 | 142 MB |
| Deno | 2.9.5 | 19 243 | 176 MB |
In this test, Bun achieves about 1.9× Node’s throughput and 2.5× Deno’s. That is relevant for investigating Express behavior on those runtimes, but it does not prove that any business API will improve in the same proportion.
Source: Bun — benchmarks
PostgreSQL: be careful comparing different drivers
The same page compares a PostgreSQL load with 100 rows per query, 100 in-flight queries, and 100 000 total queries. Bun uses its built-in SQL API, while Node and Deno use postgres.js 3.4.9.
| Runtime / driver | Queries/s | Peak RAM |
|---|---|---|
Bun 1.4 (Bun.sql) | 20 243 | 80 MB |
Deno 2.9.5 (postgres.js) | 10 406 | 181 MB |
Node.js 26.7.0 (postgres.js) | 10 000 | 168 MB |
It is not a pure runtime comparison. The database client also changes, so the result represents the implementation and configuration set. For a controlled comparison, use the same driver if available and verify that each version does exactly the same work.
Source: Bun — benchmarks
Deno 2.9 versus Deno 2.8
Deno reported these improvements on its Deno.serve server, with concurrency 100, on a dedicated Linux x86_64 machine:
| Metric | Deno 2.8 | Deno 2.9 |
|---|---|---|
| Cold start | 34.2 ms | 17.3 ms |
| HTTP realworld | 56.8k req/s | 72.4k req/s |
| HTTP text | 77.0k req/s | 85.6k req/s |
| 1 MiB response | 1 617 req/s | 1 907 req/s |
| RSS, realworld | 142 MB | 64 MB |
It is an improvement between Deno versions, not a direct comparison with Bun or Node. The benchmark’s “realworld” load receives JSON with a bearer token and returns it as JSON, but it remains a simplified application.
Source: Deno 2.9 — performance
Streams: optimizing a concrete API matters
Bun 1.4 published a Response.clone() test with a 64 MB body. In the configuration described by the project, Bun reports a 220 MB memory peak and 96 ms, Node.js 26 reports 382 MB and 230 ms, and Deno 2.9 reports 297 MB and 134 ms.
Bun’s article explains that an optimization avoids copying the full body on the second branch. It is an important advantage for that operation, not evidence that every stream manipulation is always faster.
Source: Bun 1.4 — performance
What conclusion can be drawn?
- Bun shows important advantages in several scenarios published by its team.
- Deno 2.9 improved considerably versus Deno 2.8 in the tests it published.
- Node.js remains a competitive option, but its broader advantage usually lies in ecosystem and compatibility, not in winning every microbenchmark.
- You cannot build a universal ranking by mixing tables with different scenarios.
- A real application must be measured with its dependencies, configuration, limits, and SLOs.
4. Which runtime to choose by project?
Enterprise platform with many dependencies
First option to evaluate: Node.js.
It is especially reasonable when the application depends on cloud SDKs, native modules, drivers, APM agents, and libraries whose primary support is Node. The value is in reducing uncertainty, easing diagnosis, and leveraging team experience.
Bun and Deno remain possible options, but you would need to validate every critical integration and compare the cost of maintaining the differences.
New TypeScript API
I would evaluate all three.
- Node.js with Fastify or NestJS offers a mature base.
- Bun with
Bun.serve()or Elysia can be attractive if performance or integrated tooling matter. - Deno with Hono can be interesting if web APIs, permissions, and integrated tools are valued.
The final choice depends on dependencies, the team’s architectural style, and the results of a representative test.
CLIs, scripts, and automation
Bun and Deno deserve special attention.
Their integrated tools can simplify development and startup. Deno provides a useful permission model for scripts that should not have unrestricted access to the whole system. Bun can be attractive for its speed and its integration of package manager, test runner, and bundler.
Node remains a good choice when the script depends on Node-specific packages or APIs.
Service with permission-based security requirements
I would evaluate Deno.
Its explicit permission model lets you limit access to files, network, environment, and processes. The advantage is especially useful when those limits are part of the design. It does not replace OS isolation, dependency review, or deployment controls.
WebSockets, streaming, or high throughput
Do not choose by the runtime name.
Bun publishes interesting WebSockets and stream results; Deno has improved its HTTP server; Node has a mature ecosystem. But differences depend on message pattern, payload size, fan-out, TLS, backpressure, serialization, and delivery confirmation.
Run a load test with the real topology: number of connections, messages per second, message size, latency percentiles, memory, and reconnects.
5. Frameworks: choose them separately from the runtime
| Framework | Usual environment | Why evaluate it |
|---|---|---|
| Express | Node.js | Extensive ecosystem and simplicity |
| Fastify | Node.js | Validation, serialization, plugins, and low overhead |
| NestJS | Node.js | Modular structure, dependency injection, and conventions |
| Elysia | Bun | Design tightly integrated with Bun and TypeScript |
| Hono | Node.js, Bun, Deno, and others | Web APIs and portability |
| Fresh | Deno | Web applications with server rendering and islands |
| Oak | Deno | Middleware framework with a Koa-inspired style |
The columns do not mean a framework cannot run outside its usual environment. Real compatibility depends on the APIs, plugins, and dependencies the project uses.
If portability matters, using web standards and isolating runtime APIs can reduce coupling. If you need a framework with a very specific ecosystem, prioritize compatibility and operational experience over a theoretical abstraction.
6. A fair benchmarking methodology
A useful benchmark should answer a concrete question. Before running tests, define the hypothesis: do you want to reduce cost per request, improve p99, lower memory per replica, speed up cold starts, or reduce CI time?
Controlled conditions
- Pin exact versions of runtime, framework, and dependencies.
- Use the same hardware, operating system, and container limits.
- Run the same business work and, when possible, the same framework.
- Use the same dataset, connection pool, cache, and TLS configuration.
- Verify that the load generator is not the bottleneck.
- Repeat runs and record variability.
- Separate cold start from sustained performance.
- Document code and configuration so another team can reproduce the result.
Metrics
- Throughput: completed requests per second.
- Latency p50, p95, and p99.
- CPU per request.
- RSS memory and heap evolution.
- Startup time.
- Errors, timeouts, and rejected connections.
- Stability under sustained load and during spikes.
- Infrastructure cost to meet the SLO.
Do not report only requests per second. A server can produce more throughput at the cost of a longer latency queue or memory use that forces more replicas.
Three scenarios
A. Minimal endpoint: approximates the server’s ceiling.
B. Representative API: routing, validation, authentication, serialization, and data access.
C. Production simulation: TLS, tracing, logs, container limits, sustained load, and dependency failures.
Test B or C should weigh more in the decision than test A.
7. Costs that do not appear in a performance table
Compatibility cost
A dependency that does not work can cost more than a throughput improvement. Count critical integrations, incompatible APIs, patches, and maintenance hours.
Operational cost
Evaluate support on deployment platforms, base images, health checks, graceful shutdown, workers, observability, profiling, and incident response.
Learning cost
A team expert in Node can deliver faster with Node even if another runtime is superior in a benchmark. A new team may prefer Bun’s or Deno’s integrated tooling. Learning cost is part of the decision.
Infrastructure cost
If a runtime reduces memory or CPU on a real load, it could lower the number of replicas. But that improvement must hold with dependencies, logs, and production limits. Calculate cost per traffic volume or completed task, not only peak speed.
Migration cost
Migrating a running system implies tests, rollout, observability, rollback, and training. A marginal improvement on one endpoint usually does not justify that cost.
8. Weighted decision matrix
Score each criterion for your project from 1 to 5 and assign a weight. Do not use a universal score for every team.
| Criterion | Suggested initial weight | Node.js | Bun | Deno |
|---|---|---|---|---|
| Compatibility with critical dependencies | 5 | Usually the reference | Validate APIs and native modules | Validate APIs and native modules |
| Performance on the real load | 5 | Measure | Measure | Measure |
| Security via runtime permissions | 4 | Requires additional controls | Not its main differentiator | Central differentiator |
| Integrated tooling | 3 | Modular ecosystem | Integrated | Integrated |
| Observability and operational support | 5 | Very broad ecosystem | Test APM and profiling | Test APM and profiling |
| Team experience | 4 | Depends on the team | Depends on the team | Depends on the team |
| Startup time | 2–5 depending on the case | Measure | Measure | Measure |
| Cost per served load | 5 | Measure | Measure | Measure |
| Portability across runtimes | 2–5 depending on the case | Node APIs | Better if web standards are used | Better if web standards are used |
The weights are a starting point, not an immutable recommendation. In a CLI, startup may have weight 5; in a business API dominated by SQL, startup speed may have little impact.
9. Adoption strategy with controlled risk
It is not mandatory to standardize an entire organization on a single runtime from day one. A reasonable strategy is:
- Keep the current runtime as the baseline.
- Select a small service with bounded dependencies.
- Automate functional and compatibility tests.
- Compare performance under controlled conditions.
- Test observability, containers, health checks, and shutdown.
- Deploy gradually with metrics and rollback.
- Expand usage only if the benefits are sustainable.
Avoid ending up with three runtimes by fashion. Diversity can be useful, but it adds versions, pipelines, documentation, and skills to maintain. Introduce a new platform when it solves a concrete problem.
10. Final recommendation
Choose Node.js when compatibility, ecosystem, and known operations are priorities, especially in large systems with many dependencies and integrations.
Choose Bun when integrated tooling, startup, or performance measured on your load provide a concrete benefit and you can validate critical dependencies.
Choose Deno when the permission model, web APIs, and integrated development flow fit the security and architecture requirements.
Do not turn these tendencies into absolute rules. Node.js can be more appropriate for a new API; Bun can be a great choice for an enterprise system; Deno can be a valid option even if the team uses some npm libraries. What matters is the fit between requirements and capabilities.
The best decision is not the runtime that wins a benchmark published by its own team. It is the one that meets latency, cost, security, and maintainability goals with a level of risk the team can sustain.
Measure your application. Review the dependencies. Decide according to the problem.
In this series
- Node.js: why it is still a solid bet
- Bun: is it really faster than Node.js?
- Deno: security, TypeScript, and tooling
- Final comparison (this post)
Related reading
- Node.js vs Bun vs Deno: how to choose a runtime
- NestJS, Spring Boot, and FastAPI
- Monorepo: Bun, pnpm, and Nx
- Benchmarks and architecture decisions
- Tool: which runtime suits you
- Tool: which frontend suits you
- Tool: which backend suits you
- Tool: which API protocol suits you
References
- Node.js — Release schedule
- Node.js — TypeScript
- Bun — Runtime
- Bun — Node.js compatibility
- Bun 1.4
- Bun benchmarks
- Deno 2.9
- Deno — Security and permissions
- Deno — Node and npm compatibility
- Hono
- Elysia
- Fastify benchmarks
Benchmarks are attributed to the projects that publish them. Results do not form an independent universal ranking: they vary by version, load, hardware, framework, driver, and configuration. Before a production decision, reproduce the relevant tests and add a representative test of your application.