Every so often, in a standup or a Slack thread, someone says it out loud: “do we seriously need three JavaScript runtimes? wasn’t Node enough?”. It’s an honest question. From the outside it looks like gratuitous fragmentation — the kind of thing that cost us years of framework fatigue on the frontend.
But once you understand where each one comes from, the story stops looking like a whim. The three aren’t competing for exactly the same thing: they’re three answers to three different moments in the language’s history.

node (2009): the pioneer, debts included
Ryan Dahl built Node in 2009 by gluing Chrome’s V8 engine to a non-blocking I/O layer. The idea was powerful and, in hindsight, obvious: run JavaScript outside the browser, on the server, with an event-driven model instead of a thread per request. It worked. Node ate the backend and today holds up an enormous chunk of the web.
But it got there first, and getting there first has a price. Node carries decisions that were reasonable in 2009 and are design debt today:
- CommonJS (
require) before ES Modules existed. We spent years with two incompatible module systems forced to live together. node_modules, that folder that weighs more than your operating system and became a meme in its own right.- Zero TypeScript. Running TS meant a transpiler, a build step and its configuration.
- No security model whatsoever. Any package you install — and its hundreds of transitive dependencies — can read your disk, your environment variables, or open sockets.
npm installis, in practice, running strangers’ code with your permissions.
None of that was a mistake at the time. They’re the scars of having invented the category.
deno (2018): the philosophical correction
The interesting part is who pointed out those debts first: Ryan Dahl himself. In 2018 he gave a talk titled “10 Things I Regret About Node.js” where he listed, one by one, the things he would have done differently. And instead of stopping at regret, he built the alternative: Deno, written in Rust on top of V8.
Deno is Node rewritten with the lesson learned, and its bets are exactly the corrections to the debts above:
- Security through explicit permissions. A script can’t touch the disk, the network or the environment unless you launch it with
--allow-read,--allow-netand friends. The default is not to trust. - Native TypeScript, no configuration, no build step.
- ES Modules from day one.
- APIs aligned with web standards:
fetch,Request,Response,URL. What you already know from the browser works the same on the server.
For years, the price of that purity was npm incompatibility. That changed: Deno 2, in October 2024, made peace with the ecosystem — it recognizes package.json and node_modules, imports packages through the npm: prefix, and ships deno install. The philosophical correction turned pragmatic without giving up its principles.
bun (2022): the pragmatic optimization
Bun, which Jarred Sumner introduced in 2022 and took to 1.0 in September 2023, plays a different game. It doesn’t want to correct Node’s philosophy — it wants to be a faster Node with fewer moving parts.
Where Node and Deno use V8, Bun uses JavaScriptCore, Safari’s engine, and is written in Zig (with the fresh detail that in 2026 the team started a rewrite to Rust, still in progress). But its real bet is the “all in one”: runtime, package manager, bundler and test runner in a single binary. Where a typical Node project assembles node + npm + esbuild + jest, Bun gives you all of that with one command. And bun install is absurdly fast.
In December 2025 Bun was acquired by Anthropic, which keeps it open source, MIT-licensed, with the same team. I bring it up because it changes the risk calculus: a project that looked like a one-person bet now has serious backing behind it.
If I had to sum up the three in one sentence: Node is the incumbent, Deno is the philosophical correction, Bun is the pragmatic optimization.

so, which one do I use?
Node for serious production. It’s the default nobody will question in a code review. Compatibility is guaranteed, the StackOverflow answer to any problem already exists, and your hosting provider supports it without you having to think about it. If the project matters and sleeps in production, Node is still the boring, correct answer.
Deno when security is part of the problem — running third-party code, scripts you don’t want touching your disk, multi-tenant environments —, when you want TypeScript without fighting tsconfig, or when you value a curated, standards-oriented ecosystem.
Bun for speed in development. And here’s the pattern that comes up most often: Bun as a development tool, Node in production. bun install and bun test on your machine and in CI because they’re fast; the container that ships to production runs on Node because it’s the proven one. It’s a perfectly valid and fairly common hybrid. You don’t have to marry a single runtime for the whole lifecycle.

it’s not fragmentation, it’s the browser wars all over again
Here’s my underlying opinion: what looks like wasted effort from the outside is, in reality, the best thing that could have happened to the ecosystem. And we’ve already lived through it once.
Remember when Internet Explorer had 95% of the market? The web stagnated. IE6 reigned for years without real competition, and without pressure there was no reason to improve. What unblocked everything was Firefox first and Chrome later: suddenly there was something to compare against, and browsers were forced to run.
The same thing happened with runtimes. Node spent years comfortable, nobody on its heels, moving slowly. And look at what it added since Deno and Bun exist:
- a native test runner (
node:test), stable since Node 20 — exactly what Bun was selling as an advantage; - the
--env-fileflag since Node 20.6, to load.envfiles without thedotenvdependency; - built-in TypeScript support: experimental behind a flag in Node 22, unflagged in Node 23, and on by default in Node 24, the current LTS. The runtime strips the type annotations and runs the
.tsfile directly (with limits:enumand namespaces with runtime code still require opt-in); - a stable global
fetchsince Node 21, aligned with the same web standard Deno pushed for.
That’s no coincidence. It’s competition working as it should: every feature Deno or Bun showed off as a differentiator ended up pushing Node to move. The incumbent stopped dozing off because now there’s something to compare it against.
the reassuring part: WinterTC
The underlying question remains: am I not risking writing code that only runs on one runtime and getting trapped?
Less and less, and for a concrete reason: WinterTC (formerly WinterCG). It’s the committee — since December 2024, Ecma International’s TC55, the same house that standardizes the language itself through TC39 — where Node, Deno, Cloudflare Workers and company sit down to agree on a common set of APIs. Its central work item is the minimum common API: the subset of the web platform (fetch, Request, Response, URL, streams, crypto…) that every interoperable runtime commits to supporting the same way. The stated goal is for portable code to be the norm, not the exception.

Translated: the runtime decision binds you less and less. If you write against the standard APIs, moving a service from Node to Bun or Deno looks more like swapping interpreters than rewriting.
And even in the worst case, put the risk in perspective. The cost of picking the wrong runtime is tiny compared to picking the wrong frontend framework. Switching from Node to Bun is, almost always, adjusting build commands and touching a Dockerfile. Migrating from one frontend framework to another is rewriting the application. One decision is reversible in an afternoon; the other marks you for years.
what should actually worry us
So no: three runtimes don’t strike me as madness. They strike me as health.
What would worry me is the opposite — a single runtime, nobody across the table, setting the pace of the entire ecosystem at its whim. We’ve lived that before: it was called IE6, and it took us almost a decade to climb out. Having Node, Deno and Bun racing, copying each other’s good ideas and answering to a common standard, is not the problem.
It’s exactly the insurance we’d want to have.