Choosing Between Native fetch and Axios for HTTP Requests in Modern JavaScript
Compare native fetch and Axios for modern JavaScript HTTP requests, covering bundle size, ergonomics, browser support, and concrete wrapper examples.
26 Oct 2025, 22:45 UTC

Decision: Choose an HTTP client for modern JavaScript
When a project targets ES2023 environments, the first architectural choice is often whether to rely on the built‑in fetch API or to add the axios package. The decision hinges on four practical constraints:
- Target browsers – minimum version you must support.
- Bundle budget – how many kilobytes you can afford for network code.
- API ergonomics – need for automatic JSON parsing, interceptors, timeouts, or cancellation.
- Team familiarity – existing codebase patterns and onboarding cost.
Option comparison
| Feature | Native fetch | axios |
|---|---|---|
| Bundle impact | 0 KB (built‑in) | ≈10‑15 KB minified |
| JSON handling | Manual response.json() | Automatic |
| Request/response interceptors | None (custom wrapper needed) | Built‑in |
| Timeout & cancellation | AbortController (manual) | Configurable timeout + cancellation tokens |
| Streaming response | Native ReadableStream | Supported via responseType: 'stream' |
| Browser support | All modern browsers; IE 11 requires polyfill | Same as fetch (polyfill for IE 11) |
Trade‑offs
Native fetch gives you the smallest possible payload and aligns with the platform’s evolution. The cost is boilerplate: every call needs explicit response.ok checks, JSON parsing, and an AbortController for cancellation. Teams that value zero‑dependency builds and are comfortable writing a thin wrapper often prefer this route.
Axios bundles convenience features—automatic JSON conversion, global interceptors for auth headers or error normalization, declarative timeouts, and a cancellation token API. The trade‑off is an extra ~12 KB and a runtime dependency that must be kept up‑to‑date for security patches. If your application already uses a request‑level abstraction (e.g., a service layer), Axios can reduce duplicated boilerplate across many modules.
Concrete implementation
Minimal fetch wrapper (≈30 lines)
// fetchClient.js
export async function request(url, options = {}) {
const controller = new AbortController();
const timeout = options.timeout ?? 10_000; // ms
const id = setTimeout(() => controller.abort(), timeout);
try {
const res = await fetch(url, {
...options,
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});
clearTimeout(id);
if (!res.ok) {
const err = new Error(`HTTP ${res.status}`);
err.response = res;
throw err;
}
return res.json();
} catch (e) {
clearTimeout(id);
throw e;
}
}
Usage:
import { request } from './fetchClient.js';
try {
const user = await request('/api/user/42');
console.log(user);
} catch (e) {
if (e.name === 'AbortError') console.warn('Request timed out');
else console.error(e);
}
Axios instance with interceptors
// axiosClient.js
import axios from 'axios';
const client = axios.create({
baseURL: '/api',
timeout: 10_000,
headers: { 'Content-Type': 'application/json' },
});
// Request interceptor – attach auth token
client.interceptors.request.use(cfg => {
const token = localStorage.getItem('authToken');
if (token) cfg.headers.Authorization = `Bearer ${token}`;
return cfg;
});
// Response interceptor – normalise errors
client.interceptors.response.use(
r => r.data,
err => {
if (err.response) {
// server responded with non‑2xx
return Promise.reject(new Error(`API ${err.response.status}`));
}
if (err.code === 'ECONNABORTED') {
return Promise.reject(new Error('Request timed out'));
}
return Promise.reject(err);
}
);
export default client;
Usage:
import api from './axiosClient.js';
try {
const user = await api.get('/user/42');
console.log(user);
} catch (e) {
console.error(e.message);
}
Validation steps
- Bundle size check – Run
npm run build(or your bundler) and compare the output size with and withoutaxios. Expect ~12 KB increase for the Axios build. - Functional test – Spin up a local test server (e.g.,
npx http-serveror a small Express app) that returns JSON and non‑2xx responses. Exercise both clients and verify:- Successful JSON parsing.
- Error objects contain status codes.
- Timeout aborts after the configured milliseconds.
- Browser compatibility – Open the built page in the oldest browser you must support (e.g., Chrome 89, Firefox 78, Safari 14). Confirm no
ReferenceError: fetch is not definedoraxios is not definederrors appear. - Security audit – Run
npm audit(oryarn audit) after adding Axios to ensure no known vulnerabilities are introduced.
Limitations & practical check
Both approaches assume a modern Promise‑based environment. If you must support IE 11 without a polyfill, native fetch will not work and Axios will also fail because it relies on the same underlying APIs. In that scenario a polyfill such as whatwg-fetch is mandatory for either choice. The practical way to verify the final decision is to run the validation steps above in your CI pipeline; a passing build that meets the bundle‑size budget and works in the target browsers confirms the choice.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.