Axios vs ky vs ofetch 2026: Which HTTP Client Should You Use in JavaScript?
# Axios vs ky vs ofetch 2026: Which HTTP Client Should You Use in JavaScript?
Every app talks to an API, and for over a decade the reflex answer to "how do I make the request" was one word: Axios. In 2026 that reflex is worth questioning. The native Fetch API now ships in every browser and every serious runtime — Node, Bun, Deno, Cloudflare Workers — and a new generation of tiny wrappers gives you axios-style ergonomics on top of it for a fraction of the size. This guide compares the three clients actually worth reaching for today: Axios, the battle-tested incumbent; ky, Sindre Sorhus's elegant Fetch wrapper; and ofetch, the universal client that powers Nuxt and Nitro.
If you build or sell production-ready templates, the HTTP layer is one of those quiet decisions a buyer inherits forever — so it is worth getting right.
The 30-Second Answer
The Landscape: Three Answers to One Question
The three clients are not really competing on the same axis. They are three answers to the question *"how much do I want between my code and the network, and where does that code run?"*
Axios predates modern fetch. It shipped its own adapter — XMLHttpRequest in the browser, Node's http module on the server — so it never depended on the platform having a good HTTP primitive. That is why it works everywhere back to ancient environments, and why it carries features fetch lacks. It is also why it is heavier: you are shipping a whole HTTP stack.
ky and ofetch take the opposite bet. Native fetch is now *everywhere that matters*, so both are thin wrappers that add ergonomics — retries, timeouts, throw-on-error, JSON handling, a hooks lifecycle — while inheriting fetch's streaming, AbortController cancellation, and edge compatibility for free. ky optimizes for a clean, browser-first API; ofetch optimizes for behaving the same in every runtime.
Bundle Size and Downloads
Size is the most visible difference, and it is not close.
| Client | Gzipped size | Weekly downloads | Built on |
|---|---|---|---|
| **Axios** | ~13 KB | ~50M | Own XHR/http adapter |
| **ky** | ~4 KB | ~7M | Native fetch |
| **ofetch** | <4 KB | ~27M | Native fetch |
Axios's download count reflects a decade of default status, not superiority; ofetch's high number is largely the Nuxt/Nitro ecosystem pulling it in transitively. For a browser bundle where every kilobyte on the critical path costs you, a 3x–4x size reduction from axios to a fetch-based client is real — especially on the kind of landing pages and dashboards where first load matters.
Axios: The Incumbent
Axios is the client everyone knows. Its API is comfortable, its interceptors are genuinely powerful, and there is an answer to every question you will ever have.
import axios from "axios";
const api = axios.create({
baseURL: "https://api.example.com",
timeout: 10000,
});
// Interceptor: attach auth on every request
api.interceptors.request.use((config) => {
const token = getToken();
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// Interceptor: refresh token on 401, then retry
api.interceptors.response.use(
(res) => res,
async (error) => {
if (error.response?.status === 401) {
await refreshToken();
return api.request(error.config);
}
return Promise.reject(error);
}
);
const { data } = await api.get("/users/1"); // data is already parsed JSONWhat you get: interceptors, automatic JSON transform, upload and download progress events (still axios's genuine standout — fetch's progress story is clumsier), request cancellation, timeouts, and a transform pipeline. What it costs: ~13 KB gzipped, an adapter layer instead of native fetch, and the reality that one of npm's most-installed packages is a high-value target. 2026 was a rough year for the npm supply chain, and a large, deeply-depended-on client is more to trust. If you stay on axios, pin exact versions, watch advisories, and read our companion deep-dive on Axios security vulnerabilities and how to fix them.
Use axios when: you already run it, you need interceptor-based auth flows and upload progress, or you must support environments without native fetch.
ky: The Elegant Fetch Wrapper
ky is what fetch would look like if it had shipped with sensible defaults. It is about 4 KB gzipped, has zero dependencies, and runs natively in browsers, Node, Bun, Deno, Cloudflare Workers, and Web Workers.
import ky from "ky";
const api = ky.create({
prefixUrl: "https://api.example.com",
timeout: 10000,
retry: { limit: 2 }, // automatic retries, honors Retry-After on 429
hooks: {
beforeRequest: [
(request) => {
const token = getToken();
if (token) request.headers.set("Authorization", `Bearer ${token}`);
},
],
afterResponse: [
async (request, options, response) => {
if (response.status === 401) {
await refreshToken();
return ky(request); // retry with fresh token
}
},
],
},
});
// Throws on non-2xx automatically; .json() is typed
const user = await api.get("users/1").json<User>();What you get: hooks that do everything axios interceptors do (beforeRequest, afterResponse, beforeRetry, beforeError), automatic retries that read the Retry-After header on 429 Too Many Requests, timeouts, a typed .json shortcut, and throw-on-error by default — no more forgetting to check response.ok. ky 2.0 (2026) consolidated the hook handling, improved timeout logic, and added built-in response schema validation, so you can validate a response against a schema library right in the request — a natural pairing with Zod, Yup, or Valibot.
Use ky when: you are starting a new browser or full-stack app, you want the smallest modern client with great ergonomics, or you deploy to the edge and want zero adapter surprises.
ofetch: The Universal Client
ofetch comes from unjs, the toolkit behind Nuxt and Nitro, and its whole reason for existing is to behave identically everywhere — the same code in the browser, in a Node server, and in an edge function. It is under 4 KB gzipped and reads like axios.
import { ofetch } from "ofetch";
const api = ofetch.create({
baseURL: "https://api.example.com",
retry: 2,
timeout: 10000,
onRequest({ options }) {
const token = getToken();
if (token) options.headers.set("Authorization", `Bearer ${token}`);
},
onResponseError({ response }) {
if (response.status === 401) refreshToken();
},
});
// Auto-parses JSON, throws on non-2xx
const user = await api<User>("/users/1");What you get: automatic response parsing (it detects JSON), throw-on-error with a helpful FetchError, retries, a request/response lifecycle (onRequest, onResponse, onRequestError, onResponseError), and true universality. What you give up relative to axios: there are no interceptors or plugins in the axios sense, and while TypeScript works, ky's typed .json ergonomics are a touch sharper. Use ofetch when: you live in the Nuxt/Nitro world, you want one wrapper that is genuinely runtime-agnostic, or you value an axios-familiar API in a fetch-sized package.
The Decision Table
| Need | Best pick |
|---|---|
| Smallest modern client, new browser/full-stack app | **ky** |
| One wrapper identical across browser + server + edge | **ofetch** |
| Nuxt / Nitro project | **ofetch** |
| Interceptor-based auth ecosystem, already invested | **Axios** |
| Upload/download progress events | **Axios** |
| Response schema validation built in | **ky 2.0** |
| Edge runtimes (Workers, Vercel Edge, Deno, Bun) | **ky** or **ofetch** |
| A few trivial requests, zero deps | Native **fetch** |
| Smallest supply-chain surface | **ky** (zero deps) |
Do You Even Need One?
Be honest about the simple case. Native fetch is everywhere, and for a handful of requests, a five-line wrapper is fine and ships zero bytes. The catch is the boilerplate you *will* get subtly wrong: fetch does not reject on HTTP errors (a 500 is a resolved promise), has no retries or timeouts, does not serialize/parse JSON for you, and gives you no natural home for auth across every call. The moment you need retries, timeouts, centralized auth, or consistent error handling across many endpoints, a ~4 KB fetch-based client pays for itself. This is also why an HTTP client and a data-fetching/caching layer are different tools that live together — see TanStack Query vs SWR vs RTK Query for the layer that handles caching, revalidation, and request de-duplication on top of whichever client you pick.
Migrating Off Axios
Both ky and ofetch are close enough to axios that a migration is mechanical for most codebases:
axios.create({ baseURL }) → ky.create({ prefixUrl }) (note: ky's prefixUrl does not want a leading slash on the path) or ofetch.create({ baseURL }).hooks or ofetch onRequest/onResponse.response.data → ky's .json() or ofetch's direct return value (both auto-parse).try/catch structure survives; inspect error.response (ofetch) or the thrown HTTPError (ky) instead of axios's error shape.AbortController signal instead of axios's CancelToken.Why This Matters if You Sell Templates
A buyer opening your Next.js starter or React kit judges the HTTP layer in seconds. A tiny, native-fetch client with typed responses, sane retries, and one clean place to attach auth signals a maintained, modern codebase; a heavy adapter-based client with hand-rolled error checks signals the opposite. Picking a small, edge-ready client is exactly the kind of quiet quality that turns a browse into a sale — and it sits naturally beside the stacks buyers already come to CodeCudos for. Make sure the rest of the stack pulls its weight too: see the best tech stack for web apps in 2026 and pick the runtime and API framework it runs on.
The Bottom Line
All three put a response on the promise — but they come at it from different eras and priorities, and matching the tool to *where your code runs* is the whole decision.
Reach for ky when you are starting fresh and want the smallest elegant client, ofetch when universality across runtimes is the priority, and axios when its interceptor ecosystem or upload progress genuinely earns its weight — and remember that native fetch alone is a legitimate answer for the simplest apps.
Ready to turn what you build into income? List your Next.js or React template on CodeCudos, lock down your dependencies with our Axios security guide, and make sure the whole thing reads as production-ready.
