What Axios Still Gives You Over Fetch
The Fetch API has matured significantly. It’s built into every modern browser, available natively in Node.js since v18 via Undici, and capable of handling JSON, request cancellation, and streaming. For many frontend API requests, it’s genuinely enough.
So why do teams still reach for Axios? Not out of habit, but because Axios still solves specific developer experience problems that Fetch leaves to you. Here’s where that gap actually shows up.
Key Takeaways
- Fetch resolves successfully on 4xx and 5xx responses, forcing manual
response.okchecks on every request. - Axios interceptors centralize cross-cutting concerns like auth tokens, logging, and error normalization without per-request boilerplate.
- Shared configuration via
axios.create()simplifies working with multiple APIs that need different base URLs, headers, or timeouts. - Upload progress tracking and built-in timeout options remain Axios exclusives that Fetch cannot match natively.
- For small projects with minimal HTTP needs, Fetch is the right zero-dependency default.
Axios vs Fetch: Where the Real Differences Are
1. HTTP Error Handling That Works by Default
Fetch only rejects on network failure. A 404 or 500 response resolves successfully — you have to check response.ok yourself every time.
// Fetch — you must check manually
const res = await fetch('/api/user');
if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
const data = await res.json();
// Axios — rejects automatically on 4xx/5xx by default
const { data } = await axios.get('/api/user');
In large applications with dozens of endpoints, that manual check becomes repetitive boilerplate. Axios removes most of it by default.
2. Built-In Request and Response Interceptors
This is the feature most developers cite when choosing Axios. Interceptors let you attach logic — auth tokens, logging, error normalization — once, globally, without touching individual requests.
axios.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${getToken()}`;
return config;
});
axios.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) redirectToLogin();
return Promise.reject(error);
}
);
With Fetch, you can simulate this by wrapping fetch() in a custom function. But that wrapper isn’t composable, isn’t stackable, and you’re maintaining it yourself.
3. Shared Configuration with Axios Instances
Axios lets you create isolated instances with preset base URLs, headers, and timeouts — a pattern that’s genuinely useful when your app talks to multiple APIs.
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 8000,
headers: { 'X-App-Version': '2.1.0' },
});
Replicating this cleanly with Fetch requires a wrapper class or factory function. Doable, but it’s scope you’re taking on.
Discover how at OpenReplay.com.
4. Upload Progress Tracking
Fetch still has no standardized native upload progress API in browsers. Axios exposes onUploadProgress directly, backed by XMLHttpRequest under the hood in browsers.
await axios.post('/upload', formData, {
onUploadProgress: e => {
if (e.total) {
console.log(`${Math.round((e.loaded / e.total) * 100)}%`);
}
},
});
If you need upload progress in a Fetch-only setup, you’re back to raw XMLHttpRequest. That’s a real regression.
5. Timeout Configuration Without AbortController Boilerplate
Axios accepts a timeout option directly. Fetch requires you to wire up AbortController and setTimeout manually — which works, but adds noise.
// Axios
await axios.get('/api/data', { timeout: 5000 });
// Fetch equivalent
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch('/api/data', { signal: controller.signal });
} finally {
clearTimeout(id);
}
Modern environments also support AbortSignal.timeout(), which shortens this considerably, and browser support is now broadly available.
Quick Comparison: Axios Features vs Fetch API
| Capability | Fetch | Axios |
|---|---|---|
| HTTP error rejection | Manual response.ok check | Automatic by default |
| Interceptors | Custom wrapper required | Built-in |
| Shared instances | Manual factory pattern | axios.create() |
| Upload progress | Not supported natively | onUploadProgress |
| Request timeout | AbortController + setTimeout | timeout option |
| Bundle size | 0 KB (native) | ~15 KB gzipped |
Conclusion
Fetch is not inadequate. For simple JavaScript HTTP clients — a few endpoints, no shared auth logic, no upload progress — it’s the right default. No dependency, no overhead.
But Axios earns its place when your frontend API requests grow in complexity. Interceptors, shared instances, automatic error handling, and upload progress aren’t features you’ll miss until you need them — and then you’ll want them built in, not hand-rolled. That’s what Axios still gives you.
FAQs
It depends on the project's complexity. For a handful of straightforward requests, Fetch is sufficient and avoids a dependency. For applications with shared authentication, centralized error handling, multiple API clients, or upload progress requirements, Axios saves significant boilerplate that you would otherwise reimplement yourself.
Partially. You can wrap fetch in a function that injects headers or handles errors, but you lose the stackable, composable nature of Axios interceptors. Each new concern requires modifying the wrapper directly, and chaining multiple independent interceptors becomes awkward. Most teams that try this eventually rebuild a smaller, less tested version of Axios.
Not exactly. Axios supports upload and download progress in Node.js, but the implementation differs from browsers because Node does not use XMLHttpRequest. Browser progress events are generally smoother and more granular, while some Node.js upload scenarios — especially FormData uploads — have limitations depending on the adapter and runtime.
Both are solid. Ky is a small Fetch-based wrapper that adds retries, hooks, and timeouts while staying lightweight. Got is feature-rich but Node-only. Axios remains popular because it works in both browser and Node environments with one API, has broad ecosystem support, and is familiar to most JavaScript developers.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.