Skip to main content

Open Source Axios Alternatives 2026

·OSSAlt Team
axioshttp-clientnode.jsfetchopen sourcesupply chainalternatives2026
Share:

Open Source Axios Alternatives After the 2026 Supply Chain Attack

On March 31, 2026, a malicious actor published a compromised version of axios to npm, exposing over 3 million weekly downloads to credential-harvesting code before the package was yanked. The incident lasted under six hours — but that was enough to shake developer confidence in one of the most-depended-upon npm packages of the last decade.

The aftermath is a genuine reassessment. Not just of axios, but of large, legacy HTTP clients with complex dependency trees and broad ecosystem exposure. For many teams, the breach was the nudge they needed to evaluate modern, lean, fetch-native alternatives.

Here's a practical comparison of five MIT-licensed options: got, ky, undici, wretch, and ofetch.

TL;DR

If you're on Node.js only, undici (official Node.js HTTP client, zero dependencies) or got are the strongest drop-in replacements. For browser + Node.js universal apps, ky and wretch offer the smallest attack surface. ofetch is purpose-built for Nuxt/Nitro stacks. None of them have had a supply chain incident. All are MIT licensed, actively maintained, and auditable in hours — not days.

Quick Comparison

gotkyundiciwretchofetch
GitHub Stars~14K~11K~6K~6.5K~4.5K
Weekly Downloads~50M~8M~60M+~1.5M~2.5M
Bundle SizeESM, tree-shakeable~2.5KB~35KB~2.1KB~5.2KB
Browser Support❌ Node.js only❌ Node.js only
TypeScript✅ Built-in✅ Built-in✅ Built-in✅ Built-in✅ Built-in
LicenseMITMITMITMITMIT
RuntimeNode.jsBrowser + Node.jsNode.jsBrowser + Node.jsBrowser + Node.js + Workers
Dependenciessindresorhus chain0000
Axios migrationModerateEasyManualEasyModerate

Why Developers Are Reconsidering Axios

Axios has been the default HTTP client for JavaScript since 2014. At its peak, it's a top-10 most-depended-upon npm package with over 100,000 stars on GitHub. That ubiquity is also its vulnerability.

The March 31 attack worked because of axios's size and structure. The package has a history of transitive dependencies (form-data, follow-redirects, proxy-from-env) that introduce supply chain risk at multiple layers. Even before the 2026 incident, follow-redirects had a CVE in 2024 that was widely reported. That attack targeted the same pattern: a widely-used npm package, a compromised maintainer account, and a malicious publish that slipped through automated checks.

The deeper issue: axios hasn't needed to adopt the Fetch API, which is now available in Node.js 18+, browsers, Deno, Bun, and Cloudflare Workers. Modern fetch-native clients have zero dependencies because they don't need to polyfill anything. Fewer dependencies = fewer attack surfaces.

This also creates a secondary problem: older packages like axios carry historical decisions that made sense in 2014 but are now tech debt. XMLHttpRequest as the browser transport, CommonJS modules, support for environments that have been EOL for years — each of these means more code to audit, more paths for bugs, and more surface area for a malicious actor who gains commit access.

For teams evaluating alternatives, the relevant questions are:

  • How many transitive dependencies does this package have?
  • Is it actively maintained with security patch turnaround under 48 hours?
  • Can I audit the full source in under an hour?
  • Does the package owner enforce 2FA on all npm publish accounts?

All five alternatives below score well on all four.

The Alternatives

1. undici — The Official Node.js HTTP Client

Best for: Node.js backend services, API servers, microservices

undici is the Node.js team's own HTTP/1.1 client, now bundled into Node.js core as node:http2 and available as a standalone npm package. It was built from scratch to be fast and dependency-free — no polyfills, no transitive risk.

  • GitHub: nodejs/undici — 6K+ stars, Node.js organization
  • Dependencies: 0
  • Maintained by: The Node.js project itself
  • Audit time: A few hours for the main source

The fetch() function available in Node.js 18+ is actually powered by undici under the hood. If you're using globalThis.fetch in Node.js, you're already using undici.

import { fetch } from 'undici';

// GET request
const response = await fetch('https://api.example.com/data');
const data = await response.json();

// POST with JSON
const res = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Alice' }),
});

undici also benchmarks significantly faster than axios for high-throughput server-side workloads. Because it uses HTTP connection pooling by default and doesn't go through XMLHttpRequest or legacy abstractions, raw throughput in Node.js is measurably better for API-heavy backends making hundreds of concurrent requests.

The tradeoff: undici's API is the raw Fetch API — no request/response interceptors, no automatic JSON serialization, no built-in retry logic. You'll write more boilerplate than with axios. For teams migrating from axios, wrapping undici with a thin abstraction layer is common.

When to choose: Greenfield Node.js services, containerized backends, anywhere security posture and zero dependencies matter most.


2. got — The Feature-Rich Node.js Client

Best for: Node.js CLI tools, scrapers, server-to-server APIs needing retries/pagination

got is the sindresorhus ecosystem's HTTP client, battle-tested across thousands of projects. It's Node.js only (no browser support) but offers the richest feature set of any alternative: automatic retries, pagination helpers, request/response hooks, timeout control, and excellent TypeScript types.

  • GitHub: sindresorhus/got — 14K+ stars
  • Dependencies: The sindresorhus chain (several small ESM utilities), all MIT
  • Weekly downloads: ~50M — widely used in CLI tooling and Node.js services
import got from 'got';

// GET with automatic JSON parsing
const data = await got('https://api.example.com/users').json();

// POST with JSON body
const response = await got.post('https://api.example.com/users', {
  json: { name: 'Alice' },
}).json();

// With retries
const result = await got('https://api.example.com/data', {
  retry: { limit: 3, methods: ['GET'] },
}).json();

got's hook system is the closest equivalent to axios interceptors. You can transform requests and responses, log outgoing traffic, add auth headers, and handle errors in a pipeline — familiar patterns for axios users.

The dependency situation is more complex than undici or ky. got's sindresorhus dependencies are well-audited ESM-only packages, but there are more of them. For teams where zero-dependency counts as a hard requirement, that's a dealbreaker.

When to choose: Node.js services or tools that need automatic retries, pagination, or a rich hook system similar to axios interceptors.


3. ky — Tiny Fetch Wrapper for Browser and Node.js

Best for: Frontend apps, universal (SSR) Next.js/Nuxt apps, teams that want axios-like DX on fetch

ky is a ~2.5KB fetch wrapper by sindresorhus with zero runtime dependencies. It exposes a clean API modeled after axios: retry, hooks, response shorthand (.json(), .text()), timeout support, and sensible error handling. It works identically in browser, Node.js 18+, Deno, and Bun.

  • GitHub: sindresorhus/ky — 11K+ stars
  • Dependencies: 0 (ESM, uses native fetch)
  • Bundle size: ~2.5KB minified+gzipped
import ky from 'ky';

// GET with JSON
const data = await ky.get('https://api.example.com/users').json();

// POST
const result = await ky.post('https://api.example.com/users', {
  json: { name: 'Alice' },
}).json();

// With hooks (like axios interceptors)
const api = ky.extend({
  hooks: {
    beforeRequest: [
      request => {
        request.headers.set('Authorization', `Bearer ${getToken()}`);
      }
    ],
    afterResponse: [
      async (_request, _options, response) => {
        if (response.status === 401) await refreshToken();
        return response;
      }
    ]
  }
});

For teams migrating from axios, ky is the most ergonomically similar option. The mental model maps cleanly: ky.get(), ky.post(), hooks for interceptors, .json() shorthand. The main adjustment is that ky doesn't throw on 4xx/5xx by default without throwHttpErrors: true.

When to choose: Frontend-heavy apps, universal rendering frameworks, or any context where bundle size matters and you want axios-like ergonomics without the baggage.


4. wretch — Chainable Fetch with Middleware

Best for: Frontend developers who like fluent, chainable APIs

wretch is a fetch wrapper built around a fluent, chainable API. Like ky, it wraps the native Fetch API with zero runtime dependencies, targeting ~2.1KB. Its design centers on readable chained calls and a middleware system for extending behavior.

  • GitHub: elbywan/wretch — 6.5K+ stars
  • Dependencies: 0
  • Bundle size: ~2.1KB
import wretch from 'wretch';

// GET with auth
const data = await wretch('https://api.example.com/users')
  .auth(`Bearer ${token}`)
  .get()
  .json();

// POST
const result = await wretch('https://api.example.com/users')
  .post({ name: 'Alice' })
  .json();

// Error handling by status
await wretch('https://api.example.com/data')
  .get()
  .notFound(error => console.log('404:', error))
  .unauthorized(error => redirect('/login'))
  .json(data => render(data));

wretch's chainable error handling by HTTP status code is genuinely ergonomic — more granular than axios's single error catch. The middleware system supports addon packages (wretch-middlewares) for retry, delay, and throttle logic, keeping the core tiny while allowing extension.

When to choose: Teams who prefer fluent builder-style APIs and want precise per-status error handling in frontend apps.


5. ofetch — Built for Nuxt, Runs Everywhere

Best for: Nuxt.js projects, Nitro-based backends, Cloudflare Workers

ofetch is UnJS's HTTP client — the same team behind Nuxt.js, Nitro, and h3. It's the default HTTP client in the Nuxt 3 ecosystem and designed to work universally across browser, Node.js, and Cloudflare Workers without polyfills.

  • GitHub: unjs/ofetch — 4.5K+ stars, unjs organization
  • Dependencies: destr (a tiny safe JSON parser from the same org)
  • Bundle size: ~5.2KB
import { ofetch } from 'ofetch';

// GET — automatic JSON parsing
const data = await ofetch('https://api.example.com/users');

// POST
const result = await ofetch('https://api.example.com/users', {
  method: 'POST',
  body: { name: 'Alice' },
});

// With base URL and interceptors
const api = ofetch.create({
  baseURL: 'https://api.example.com',
  onRequest({ request, options }) {
    options.headers = { Authorization: `Bearer ${getToken()}` };
  },
  onResponseError({ response }) {
    if (response.status === 401) router.push('/login');
  }
});

ofetch's $fetch pattern (the $ prefix) is idiomatic in Nuxt 3 — if you're in that ecosystem, you're already using it. For non-Nuxt projects, ofetch works but brings more context than teams might need. The destr dependency is tiny and maintained by the same organization, keeping supply chain risk low.

When to choose: Nuxt 3 or Nitro-based projects, or Cloudflare Workers where universal runtime support matters.


Migration Cheatsheet: Axios → Alternatives

The most common axios patterns and their equivalents:

// ─── AXIOS ──────────────────────────────────────────────
import axios from 'axios';

// GET
const { data } = await axios.get('/api/users');

// POST with JSON
const { data } = await axios.post('/api/users', { name: 'Alice' });

// Instance with base URL + auth header
const api = axios.create({
  baseURL: 'https://api.example.com',
  headers: { Authorization: `Bearer ${token}` },
});

// Request interceptor
api.interceptors.request.use(config => {
  config.headers.Authorization = `Bearer ${getToken()}`;
  return config;
});

// Error handling
try {
  await axios.get('/api/data');
} catch (err) {
  if (err.response?.status === 404) console.log('Not found');
}


// ─── KY EQUIVALENT ──────────────────────────────────────
import ky from 'ky';

const data = await ky.get('/api/users').json();
const data = await ky.post('/api/users', { json: { name: 'Alice' } }).json();

const api = ky.extend({
  prefixUrl: 'https://api.example.com',
  hooks: {
    beforeRequest: [req => req.headers.set('Authorization', `Bearer ${getToken()}`)],
  },
});

try {
  await ky.get('/api/data').json();
} catch (err) {
  if (err.response?.status === 404) console.log('Not found');
}


// ─── GOT EQUIVALENT (Node.js only) ──────────────────────
import got from 'got';

const data = await got.get('/api/users').json();
const data = await got.post('/api/users', { json: { name: 'Alice' } }).json();

const api = got.extend({
  prefixUrl: 'https://api.example.com',
  hooks: {
    beforeRequest: [opts => { opts.headers.authorization = `Bearer ${getToken()}`; }],
  },
});

Supply Chain Security: What to Evaluate

For every HTTP client (or any npm dependency), a practical audit checklist:

  • Dependency count: npm ls --prod — fewer is better
  • Org ownership: Is it maintained by an individual or an organization with 2FA enforcement?
  • Publish frequency: Irregular spikes in publish activity are a red flag
  • Source auditability: Can you read and understand the full source in an hour?
  • Lock file integrity: Commit your package-lock.json or pnpm-lock.yaml and use npm ci in CI

The axios incident highlighted that 2FA enforcement on npm maintainer accounts is not uniform. npm now offers granular publish access controls — packages where all maintainers have 2FA enforced display a verified badge. All five alternatives covered here have this protection in place.

Beyond 2FA, npm's provenance attestation (available since npm 9) allows maintainers to cryptographically link published packages back to their CI/CD pipeline. When a package is published with provenance, you can verify that the .tgz on the registry matches the source code in the linked GitHub Actions run. got, ky, and undici all publish with provenance attestation. If supply chain security is a hard requirement, this is worth verifying with npm audit signatures before adopting any new dependency.

When to Use Which

ScenarioRecommendedWhy
Node.js microservice, zero-dep requiredundiciOfficial, 0 deps, ships with Node
Node.js with retries, pagination, interceptorsgotRichest feature set for Node
Universal app (Next.js, Remix, browser)ky0 deps, axios-like DX, works everywhere
Frontend, chainable/fluent API preferencewretchCleanest chaining, per-status error handling
Nuxt 3 / Nitro / Cloudflare WorkersofetchNative UnJS ecosystem fit
Still want axiosPin to last known-good version, audit deps, monitor advisories

Further Reading

If you're auditing your dependency tree after the axios incident, see the self-hosting security checklist for broader supply chain hygiene practices, and why open source self-hosting can be more secure than managed SaaS. For a broader overview of tools in the developer tooling space, see best open source developer tools 2026.

The SaaS-to-Self-Hosted Migration Guide (Free PDF)

Step-by-step: infrastructure setup, data migration, backups, and security for 15+ common SaaS replacements. Used by 300+ developers.

Join 300+ self-hosters. Unsubscribe in one click.