JH← Back to blog

Node.js Doesn't Need axios Anymore, and Most Teams Haven't Noticed

Native fetch and Web Streams are stable and production-ready in Node.js, closing a gap that's had backend JavaScript pulling in third-party packages for over a decade. Here's what to actually remove from your dependency tree.


I still see new Node.js projects pulling in axios or node-fetch in the first commit, out of pure habit. That habit is now costing you dependencies you don't need. Node's native fetch implementation is production-ready, fetch().body returns a real Web Stream, and the packages that used to be mandatory are increasingly the thing you remove during a dependency audit, not the thing you add on day one.

What's actually stable

Node's fetch supports the full range of what you'd expect — FormData handling, streaming request and response bodies — and it's optimized enough now that "native fetch is slower" isn't a real argument anymore. The bigger deal is that fetch().body returns a genuine Web Stream, meaning ReadableStream, WritableStream, and TransformStream are first-class inside Node itself, not an approximation bolted on to satisfy a spec.

Node also ships .toWeb() and .fromWeb() utilities that let you pipe data between traditional Node streams and Web Streams. That's the detail that actually matters for adoption — it means you don't need a disruptive rewrite to start using Web Streams in new code, even if your existing stream logic predates the standard.

On the language side, native TypeScript support stabilized in Node 24, which removes ts-node or a separate compile step from a lot of project setups. If you're picking a baseline for new work, Node 24 is the reasonable target now, with Node 22 — Active LTS since late 2024 — as the floor for systems still under support constraints you can't move off yet.

Why this is actually about supply chain, not convenience

The framing I'd push back on is "this is a nice convenience update." It's more specific than that: every dependency you remove is one less thing that can get compromised in a supply chain attack, one less thing that breaks on a version bump, one less thing sitting in your node_modules that someone eventually has to audit for a CVE. npm supply-chain incidents haven't slowed down. Fewer dependencies for the same functionality is a real security posture improvement, not just a smaller package-lock.json.

axios and node-fetch aren't broken — that's worth saying clearly. The functionality they provided is now built into the runtime with browser-matching semantics, which means for a new project, pulling them in is adding a dependency to get something you already have.

The part that's easy to miss: cross-runtime portability

Because native fetch and Web Streams follow the same standard everywhere, the same streaming code now runs largely unchanged across Node, Bun, Deno, Cloudflare Workers, and the browser. If your organization is running JavaScript across multiple runtimes — which is increasingly the normal case with edge and serverless architectures — that standardization is a real cost reduction on maintaining environment-specific code paths, not just a nice architectural property. Web Streams also outperform legacy Node streams on most workloads, so there's a genuine performance case for migrating, not just a purity argument.

What to actually do

Audit new projects before you add axios or node-fetch out of habit — for basic HTTP requests, native fetch covers it in most cases now, and adding the dependency is the thing that needs justification, not the reverse.

For existing stream-heavy code, don't attempt a full rewrite. Use .toWeb() and .fromWeb() to adopt Web Streams incrementally in new code paths, and leave stable legacy stream logic alone until there's an actual reason to touch it.

Standardize new builds on Node 24 where your infrastructure allows it, to get native TypeScript support and the most mature fetch and streams implementation, keeping Node 22 as the floor for systems still under active support constraints.

Re-run your dependency audits with this specifically in mind. Packages added two or three years ago to fill a gap the runtime has since closed are exactly the kind of thing that survives audit after audit because nobody re-checks whether the original reason still applies. It usually doesn't anymore.