Node.js after Deno, Bun, and Go: what it’s still best at and what’s leaving

Elena Vasquez

Elena Vasquez

August 28, 2026

Node.js after Deno, Bun, and Go: what it’s still best at and what’s leaving

I used to defend Node with a sentence that is no longer true: it is the only JavaScript runtime you can actually ship. That was a 2018 sentence. In 2026 I can ship Deno on a script I care about permissions for, Bun in CI when I am tired of waiting on npm install, and Go on the service that is a pipe with a binary. Node did not vanish. The monopoly did.

What I want is a map, not another eulogy. After Deno, Bun, and Go got real, some jobs still belong to Node. Some jobs left and are not coming back. Some jobs people keep announcing as “over” are still Node’s, because the alternative is a tweet and a weekend, not an on-call rotation.

This is not the same question as whether I would still start a new backend in Node.js. That is a Monday-morning product call. This is the territory call: what is Node still best at, and what has actually left the building.

The three things that showed up

Deno showed up as “Node with the sharp edges filed off”: permissions, a standard library, TypeScript without a ceremony, and a security story you can explain to someone who has been burned by a postinstall script. It is a better default for a lot of scripts. It is not a better default for “the Stripe SDK and the Datadog agent and the six-year-old native addon.”

Bun showed up as “what if the whole toolchain were fast.” Install, test, run, bundle. I have felt that on a cold CI runner. I have also felt the moment a native module, an OpenTelemetry exporter, or a vendor SDK did something Node-shaped and Bun did something else. Speed is a real gift. Compatibility is the bill.

Go did not show up. Go was already there. What changed is that enough JavaScript teams finally admitted some services were never JavaScript problems. Ingest, proxy, transform, write. Small binary. One file to scp. That job always looked like Go. Node used to win it anyway because the team already knew callbacks. That excuse got weaker every year someone on the team learned go test.

Those three did not replace Node. They partitioned it.

What Node is still best at

Node is still best at the product API that talks to other companies’ computers. Stripe, Clerk, Auth0, Resend, Slack, Shopify, AWS. If a vendor has one SDK they actually maintain, it is still often the JavaScript one. I have written the “thin Go client from their OpenAPI file” version of this. I have also spent a week discovering the undocumented header the Node SDK was already sending. On week two of a product, I want the SDK that has been yelled at by thousands of other backends.

Node is still best at the backend the frontend team will have to read. Shared TypeScript is not a slogan for me. It is the difference between a coupon bug that dies in an afternoon and a coupon bug that becomes a Jira ticket because the types live in two languages and the date is a string in one of them. A BFF in front of an older core is still a Node-shaped job on most teams I sit with. The screen wants JSON that looks like the screen. The people who care about the screen write TypeScript.

Node is still best at the boring production path. Official images. APM agents that assume Node. Heap snapshots. node --inspect. A decade of “this is how we run it in Kubernetes.” I can get a Fastify service from laptop to a container with fewer surprises than I can get the same service onto a newer runtime that promises the same npm ecosystem and then blinks at a native addon. Boring is a feature when the page is yours.

Node is still best at hiring for a first backend. The pool is messy. A lot of it is Next.js API routes and not much else. It is also the deepest pool of people who can read a webhook handler on Thursday. Deno’s pool is smaller and more opinionated. Bun’s pool is “people who like fast tools.” Go’s pool is real and I like it, and I still cannot always fill a seat in two weeks in the same city at the same rate.

Developer desk with a terminal, coffee, and afternoon window light

What Deno actually took

Deno took the script. That is not an insult. A lot of “backends” in 2019 were scripts that grew an Express server. Deno is what I reach for when I want to run TypeScript with permissions, a lockfile I understand, and no node_modules archaeology. One-off migrations. Internal CLIs. A webhook tester. A job I will run from a laptop and then from CI.

Deno also took the “secure by default” argument. Node’s permission model arrived late and still feels bolted on compared to Deno’s original pitch. If I am handing a contractor a script that can see the filesystem, I would rather start in a runtime that asks. I will not pretend most production Node apps use Node’s permission flags. We still run as “the process can do everything the user can.” Deno made that embarrassment visible.

What Deno did not take is the vendor-shaped product API. Every time I have tried, I have hit a library that is Node-first and Deno-adjacent. Sometimes it works through compatibility. Sometimes it works until it does not, usually in OpenTelemetry or a binary dependency. I will keep using Deno where the standard library is enough. I will not bet a checkout on compatibility.

What Bun actually took

Bun took the wait. npm install on a clean runner used to be a coffee. Bun made it a glance. The test runner is good enough that I have moved packages to it without making a religion of it. If your pain is “CI is the product,” Bun is a serious answer.

Bun also took the “JavaScript can be a fast HTTP server” talking point away from Node. Fastify on Node is fast. Bun’s HTTP is faster in the hello-worlds people screenshot. I do not pick production runtimes from hello-worlds. I pick them from “does this Datadog exporter still work after a patch Tuesday.” That is why I still have not moved a customer API onto Bun as the runtime, even when I like it in the toolbox. The honest bake-off lives in Bun versus Node for backend APIs. The short version for this map: Bun took the narrative and some of the toolchain. It has not taken the production default on teams I trust with money.

What is leaving, because of Bun, is the idea that Node is the only way to run JS quickly. That idea deserved to leave. What is not leaving is “the runtime the ecosystem assumed when it compiled the addon.”

What Go actually took

Go took the pipe. I do not start new ingest, proxy, or transform services in Node unless the team cannot staff Go and the traffic is a rounding error. A static binary, a small container, goroutines that look like the problem — that job left Node for me around the third time I explained worker threads to someone who just wanted a fan-out.

Go also took the sidecar. Service mesh adjacent things, little auth proxies, the process you drop next to something else. Node sidecars into Java shops have been some of my worst pages. The Node was fine. The rotation was not. Go is a language an existing systems team will touch at 3 a.m. more readily than they will touch npm.

Go took CLIs that need to be installed by people who do not have Node on their path. I used to ship Node CLIs with a shebang and a prayer. I still do for internal tools in a JS repo. For anything a stranger will install, I want a binary.

What Go did not take is the first version of a product whose UI is already TypeScript. I have watched that rewrite start too early. I have watched it finish with two date formats and a generated client someone edited by hand. Go can wait until a profiler or a team boundary says so.

Small compiled deploy artifact next to a container on an industrial desk

What is leaving Node even if Node stays

Some losses are cultural, not runtime switches. They still matter, because they change what you should stop teaching as the default.

Express as the teaching default is leaving, and it should. I can still read an Express app. I do not start one. Middleware order as a personality trait is a tax I will not pay on a greenfield. Fastify or Hono for HTTP. Nest only if the team already thinks in modules. The “hello Express” tutorial is now a historical document with a lot of leftover app.use.

“JavaScript everywhere” as a unique pitch is leaving. The edge is Deno, Cloudflare Workers, Bun, and a pile of other isolates. The frontend is TypeScript. The mobile app might be TypeScript. That used to mean “so the backend should be Node.” Sometimes it still does, because of sharing. Sometimes it means the backend can be anything that speaks JSON, because the shared part is the schema, not the process. I still like a shared language. I no longer pretend it is the only grown-up option.

Node as the default for every script in the repo is leaving. I used to add a scripts/ folder of Node files because it was already there. Now I ask whether the script needs the ecosystem. If it needs Stripe, it stays Node. If it needs to rename files and call ffprobe, it can be Deno, Bun, or a 40-line Go program. npm is not a moral requirement.

Node as the default for new systems-shaped services is leaving on teams that can hire otherwise. I will still do it for a six-person TypeScript shop. I will not do it in a fleet that already writes Go just because the last service was Node. Ownership of the page beats continuity of the runtime.

The “Node is slow” dunk is also leaving, or it should. Node was never slow at waiting. It was slow at crunching, and it was easy to stall. The dunk confused those two. Bun made the dunk louder by winning microbenchmarks. The useful sentence is still: Node is a great waiter and a mediocre worker. That was true in 2016. It is true now. The difference is we have somewhere else to put the worker.

What people keep saying left, that did not

People keep saying the product API left for Bun. I have not seen it leave in companies that have a real vendor list and a real APM bill. I have seen prototypes leave. Prototypes are allowed to be brave.

People keep saying Node left for Deno because TypeScript is “native” now. Node’s TypeScript story in 2026 is fine for shipping. The remaining gap is not “can I run .ts.” The remaining gap is the ecosystem that compiled against node: and require and a specific ABI. That gap is smaller than it was. It is not gone. I do not move a checkout to close a gap that is “smaller.”

People keep saying Node left for Go because “scale.” Most of the teams who said that did not have a scale problem. They had a hiring fashion problem or a CPU problem they had not extracted into a worker. Scale is a measurement. It is not a personality.

People keep saying npm left because of supply-chain fear. The fear is justified. The leaving is not complete. We pin, we vendor the scary bits, we stop installing cute names. We still live there. Deno’s npm compatibility and Bun’s npm compatibility are admissions, not funerals.

How I assign work in 2026

When a new piece of work shows up, I ask what it is, not what language I like this month.

  • Product HTTP, webhooks, vendor SDKs, a UI team that will patch it: Node.
  • Script with permissions, or a CLI inside a JS repo that should not need 200MB of node_modules: Deno, sometimes Bun.
  • CI pain, tests, installs, local speed: Bun in the toolbox, Node still in production until compatibility is boring.
  • Pipe, proxy, sidecar, binary for strangers, CPU-shaped worker: Go, unless the team cannot staff it.

I do not assign “the whole company” to one cell. I assign the next process. The API can stay Node while the transcoder leaves. The script can leave while the checkout stays. That is the partition working. The failure mode I keep seeing is the opposite: one runtime for every new file because “we already have it,” including the file that stalls the event loop on a PDF.

I also assign ownership before I assign a runtime. If the frontend team will own the BFF, I am not dropping Go on them to feel serious. If the platform team will own the ingest, I am not dropping Node on them to feel consistent with the API. The map above is technical. The page is social. Both have to be true or you get a sidecar only one person can debug.

What I would tell a team that is already on Node

Do not rewrite the API because Deno exists. Do not rewrite it because Bun is fast. Do not rewrite it because a staff engineer likes Go. Extract the worker that is a CPU. Move the script that does not need npm. Put new pipes in Go if you can hire for them. Keep the checkout where the SDKs and the APM and the people who get the page already are.

If you are greenfield and TypeScript-heavy, Node is still the production default I expect to see. If you are greenfield and the product is a pipe, stop pretending you need JavaScript in the middle. If you are greenfield and the first artifact is a script, stop opening package.json on reflex.

The thing that is leaving is not Node. It is Node-as-the-answer-to-every-process. That answer was lazy and, for a decade, it was also rational. The alternatives were incomplete. They are not incomplete in the same way now. They are incomplete in specific ways, and Node is strong in the leftover specific ways: vendors, shared TypeScript, the boring path to production, and a hiring pool you can actually fill.

I will keep using Node for those. I will keep letting the other three take the jobs they already won. The teams that get hurt in 2026 are the ones still picking a runtime like it is a sports team, and the ones still putting every new file in the same process because the first file was JavaScript.

More articles for you