Making HTTP requests is something almost every React app needs to do. It's a fundamental part of building modern web applications, from pulling in user data and submitting forms to syncing with a backend. And while it seems like a routine task, the tools you choose can really affect your app’s performance, code clarity, and developer experience.
Actually, in React, there are 2 common choices for handling HTTP requests: Fetch API vs Axios. Are you still confused about what to choose? In this blog, I'll compare them in practical examples and show how Axios and Fetch perform in real projects. Not just a checklist comparison, you’ll see side-by-side code, learn how error handling differs, and get tips on migrating and optimizing for performance.
>> Read more:
- Mastering React Query: Simplify Your React Data Flow
- Mastering React Context API for Streamlined Data Sharing
Fetch API vs Axios: Quick Overview
- Fetch API is a built-in browser feature that lets you send HTTP requests without any additional dependencies. It's lightweight, promise-based, and works well in modern browsers.
- Axios is a popular third-party HTTP client originally built on XMLHttpRequest, but now partially aligned with Fetch. It offers a nicer API and hides a lot of the low-level details, which makes it great for more complex use cases.
Let's have a quick look at how Fetch and Axios compare across the most important areas:
Feature | Fetch API | Axios |
---|---|---|
Installation | Native, no installation required | Needs to be installed via npm |
Syntax | More verbose, lower-level | Cleaner and easier to read |
Error Handling | Doesn’t throw on HTTP errors, must check .ok manually | Automatically throws for 4xx/5xx responses |
Request Setup | Must configure headers and options manually | Defaults like Content-Type are handled automatically |
Response Parsing | Requires calling response.json() | Parses automatically as response.data |
Interceptors | Not supported, you’ll need custom wrappers | Supports request and response interceptors natively |
Timeout Support | Requires AbortController | Built-in support with timeout option |
File Upload Handling | Manual setup with FormData and headers | Automatically detects and handles FormData |
At first, both seem pretty similar. But as your app grows from simple GET
requests to things like handling authentication headers, error interceptors, and cancellation tokens, you’ll start noticing how Axios can help reduce boilerplate and keep things cleaner.
Fetch API vs Axios: Detailed Comparison in Practical Scenarios
Case Study 1: Basic GET and POST — Readability and Error Handling
To understand the fundamental trade-offs between Fetch and Axios, let’s start with the most common use case: fetching a list of blog posts and submitting a new post to an API.
This case covers the two most frequent HTTP verbs:
GET
— used to retrieve existing data from the server.POST
— used to send new data to the server.
While both Axios and Fetch can handle this, they differ significantly in how much boilerplate, manual checking, and response parsing they require.
Using Fetch API
// GET request
const getPosts = async () => {
try {
const response = await fetch('<https://jsonplaceholder.typicode.com/posts>');
if (!response.ok) throw new Error('Failed to fetch posts');
// The .ok check is crucial because fetch() does not reject on HTTP error statuses.
if (!response.ok) {
throw new Error(`Failed to fetch posts: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
// This catch block only runs for network errors or the explicit throw above.
console.error('Fetch error:', error);
}
};
// POST request
const createPost = async (post) => {
try {
const response = await fetch('<https://jsonplaceholder.typicode.com/posts>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(post),
});
if (!response.ok) throw new Error('Failed to create post');
return await response.json();
} catch (error) {
console.error('Create error:', error);
}
};
With Fetch, every call requires explicit setup:
- You must manually set headers like
Content-Type
. - You must manually check
response.ok
for HTTP status validation. This is becauseWorkspace()
only rejects its promise on network failures, not on HTTP error statuses like404
or500
. Without this check, yourcatch
block won't run for failed API calls. - You must call
response.json()
to parse the response body.
This verbosity increases the likelihood of bugs or inconsistency, especially when duplicated across multiple components.
Using Axios
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: '<https://jsonplaceholder.typicode.com>',
});
// GET request
const getPosts = async () => {
try {
const { data } = await axiosInstance.get('/posts');
return data;
} catch (error) {
console.error('Axios error:', error.message);
}
};
// POST request
const createPost = async (post) => {
try {
const { data } = await axiosInstance.post('/posts', post);
return data;
} catch (error) {
console.error('Create error:', error.message);
}
};
Axios abstracts away the manual steps:
- Headers like
Content-Type: application/json
are automatically applied for JSON bodies. - It automatically parses the response (
data
) so there's no need to call.json()
. - Errors from failed HTTP status codes (4xx, 5xx) are automatically caught in
catch
.
This makes Axios more concise and expressive, especially as your HTTP logic grows in complexity.
Observations
Feature | Fetch | Axios |
---|---|---|
Syntax | Verbose, manual parsing | Cleaner, intuitive |
Error Handling | Requires | Built-in error detection |
Request Setup | Manual headers/config | Declarative and centralized |
JSON Handling | Manual | Implicit via |
DX Consistency | Inconsistent across app | Easy to standardize |
For small utilities or quick scripts, Fetch may be enough. But in scalable applications where consistency, readability, and shared configuration matter, Axios provides cleaner defaults that scale better across large teams.
Case Study 2: Auth Tokens, Interceptors, and Error Management
In modern applications, handling authentication and authorization is not just about passing a token. You need a robust strategy to:
- Automatically attach auth tokens to every request
- Handle token expiry or invalid sessions
- Redirect users to login on
401 Unauthorized
errors - Avoid repetitive code across components or services
This is where Axios's built-in interceptors offer a powerful abstraction. With Fetch, you'd have to build this logic yourself using custom wrappers.
Fetch: Manual Auth Handling with Wrapper
const fetchWithAuth = async (url, options = {}) => {
const token = localStorage.getItem('authToken');
const headers = {
...options.headers,
Authorization: token ? `Bearer ${token}` : '',
};
const response = await fetch(url, {
...options,
headers,
});
if (response.status === 401) {
window.location.href = '/login';
}
return response;
};
// Usage
await fetchWithAuth('/api/posts', { method: 'GET' });
This works, but has drawbacks:
- You must ensure every request uses the wrapper.
- Global logic (e.g. redirects, retries, logout) is harder to enforce consistently.
- The abstraction leaks into the app’s business logic, making maintenance harder.
In larger codebases, this leads to duplication, inconsistent behavior, and more room for bugs.
Axios with Interceptors
// setupAxios.ts
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: '/api',
});
axiosInstance.interceptors.request.use((config) => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Handle expired token globally
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default axiosInstance;
With this setup:
- All requests include the auth token without repeating logic.
- A 401 response triggers a global redirect to login.
- You gain a single, reusable place to handle auth concerns.
This approach also keeps your React components focused on rendering UI instead of networking concerns.
>> Read more: 7 Necessary Network Debugging Tools for DevOps Experts
Observations
Feature | Fetch | Axios |
---|---|---|
Auth Token Handling | Requires manual wrapper | Interceptors handle globally |
Error Interception | Requires manual | Global |
Reusability | Requires discipline to enforce | Centralized and DRY |
DX & Scalability | Fragile at scale | Battle-tested and extensible |
In real-world applications, use interceptors in Axios to manage cross-cutting concerns like:
- Auth headers
- Retry logic for transient errors
- Centralized error reporting (e.g., to Sentry or LogRocket)
For Fetch, you would need to recreate this middleware system manually, which adds unnecessary complexity unless you're building a highly customized stack or working in a constrained environment.
Code Reference: Fetch vs Axios Cheatsheet
Below is a side-by-side reference of common HTTP tasks, comparing how Fetch and Axios handle each scenario. This reference is especially useful for teams migrating from one client to another or establishing internal coding standards.
Task | Fetch Example | Axios Example | Notes |
---|---|---|---|
Basic GET | fetch('/api') → response.json() | axios.get('/api') → response.data | Axios returns the parsed data directly, reducing verbosity |
POST JSON | Set headers + JSON.stringify() manually | Pass plain JS object; headers handled automatically | Axios automatically sets Content-Type: application/json |
Add Auth Token | Add Authorization in every request | Use interceptors.request to inject globally | Axios offers global token injection without polluting business logic |
Handle 401 Globally | Check response.status manually | Use interceptors.response to redirect or log out | Centralized handling leads to more consistent UX |
Cancel Request | Use AbortController , requires extra setup | Also use AbortController | Both now support AbortController ; Axios aligns with modern standards |
File Upload (multipart) | Use FormData , set headers manually | Pass FormData directly, Axios infers headers | Axios simplifies multipart handling and boundary management |
Timeout Handling | Requires AbortController | Pass timeout option (ms) in config | Axios provides built-in timeout support per request or globally |
For teams, having a clear, shared abstraction (e.g. httpClient.js or apiService.ts) that encapsulates these patterns is essential. It avoids repeating setup logic and creates a single source of truth for networking behavior. Axios’s flexibility makes it a more natural fit for this kind of layering.
Fetch vs Axios: Which One Should You Choose?
Choosing between Axios and Fetch is less about which is better, and more about which fits your project’s scope, team maturity, and scaling needs.
Here’s a simplified decision matrix to guide your choice:
Use Case | Recommendation |
---|---|
Small apps with few API calls | Fetch — lightweight and native |
Complex apps with auth, retry logic | Axios — centralized abstraction |
Teams working collaboratively | Axios — improves consistency |
Need browser compatibility and edge handling | Axios — more robust defaults |
When Fetch Makes Sense
Fetch is ideal when:
- You’re building small to medium apps or internal tools.
- You want zero external dependencies to keep your application's bundle size as small as possible. Fetch is built into modern browsers (0kb), whereas Axios adds a small package overhead.
- Your HTTP logic is straightforward and rarely reused.
However, without wrappers or abstractions, Fetch becomes unwieldy as requirements grow.
When Axios Scales Better
Axios shines in:
- Production-grade apps with authentication flows.
- Shared HTTP behaviors like interceptors, timeouts, and error logging.
- Teams where consistency, reusability, and readability matter.
By encapsulating logic in axiosInstance
and using interceptors, you build a centralized HTTP layer, making your code easier to maintain and debug across environments.
Migration Strategy from Fetch to Axios
If you’re using Fetch but need to scale:
- Wrap Fetch calls in utilities or custom hooks to isolate logic (
useFetch
). - Identify cross-cutting concerns (auth tokens, error handling).
- Introduce Axios via
axios.create()
and centralize logic in one client. - Replace Fetch calls incrementally, starting with the most complex endpoints.
- Test and monitor before deprecating existing Fetch wrappers.
This strategy allows for progressive adoption without breaking your app, enabling teams to transition cleanly while retaining stability.
Final Tips
Here are a few real-world recommendations to help you make the most of your chosen HTTP client:
- Abstract early. Whether using Axios or Fetch, create wrapper functions or custom React hooks (
useApi
,useAxios
,useFetch
) to encapsulate logic. This keeps your components focused on UI and reduces duplication. - Avoid mixing clients. Choose either Fetch or Axios at the project level. Mixing both leads to inconsistency in response handling, error formats, and testing strategies.
- Standardize error handling. Use interceptors (Axios) or wrapper functions (Fetch) to implement global error strategies—such as logging, redirecting on 401s, or showing toast notifications.
- Centralize config. For Axios, maintain a single
axiosInstance
. For Fetch, use utility functions with sane defaults for headers, base URLs, etc. - Consider Ky for hybrid needs. If you want a lightweight Fetch-based client with a better DX, Ky offers a modern, extensible API with Axios-like ergonomics.
- Profile performance. Tools like Chrome DevTools, React Profiler, or custom metrics should guide your decision, not assumptions. Over-optimizing for abstraction can hurt if your app only makes a few network calls.
Summary
- Use Fetch for simplicity, small apps, or zero-dependency environments.
- Choose Axios when your app needs token management, interceptors, centralized error handling, or improved DX.
- Start with wrappers, evolve toward abstraction. The more your team and app scale, the more Axios will pay off in long-term maintainability.
By making deliberate choices early, you’ll reduce technical debt and build a network layer that’s both powerful and predictable.
- coding
- web development