Axios is a promise-based HTTP client for browsers and Node.js. It provides request and response interceptors, automatic JSON transformation, configurable instances, timeouts, cancellation through AbortController signals, and a consistent error object. Modern fetch is built into browsers, so Axios is a dependency choice rather than a requirement for AJAX.
After this lesson, you can create a scoped Axios instance, pass query parameters separately from request data, classify Axios errors, and keep interceptors from becoming hidden application logic.
Axios is a popular, Promise-based HTTP client for both the browser and Node.js. It automatically serializes request bodies to JSON, automatically parses JSON responses, and rejects the Promise for HTTP error status codes - fixing one of the most common fetch() gotchas.
Install via npm: npm install axios, or include via CDN:
// GET - params are serialized automatically
axios.get('/api/users', {
params: { page: 1, limit: 10, active: true }
})
.then(response => {
// response.data is already parsed JSON
console.log(response.data);
console.log(response.status); // 200
console.log(response.headers); // response headers
});
// POST - body is automatically JSON.stringify'd
axios.post('/api/users', {
name: 'Alice',
email: 'alice@example.com'
})
.then(res => console.log('Created:', res.data));
// PUT
axios.put('/api/users/1', { name: 'Alice Updated', email: 'alice@example.com' })
.then(res => console.log('Updated:', res.data));
// DELETE
axios.delete('/api/users/1')
.then(res => console.log('Deleted, status:', res.status));
// Full config object
axios({
method: 'post',
url: '/api/login',
headers: { 'X-Custom-Header': 'value' },
timeout: 10000, // 10 seconds
data: { username: 'alice', password: 'secret' }
})
.then(res => console.log(res.data))
.catch(error => {
if (error.response) {
// Server responded with a status outside 2xx
console.error('HTTP Error:', error.response.status);
console.error('Data:', error.response.data);
} else if (error.request) {
// Request was made but no response received (network error)
console.error('No response received:', error.request);
} else {
// Something went wrong setting up the request
console.error('Request setup error:', error.message);
}
});
// Create a reusable Axios instance with defaults
const api = axios.create({
baseURL: 'https://api.example.com/v1',
timeout: 8000,
headers: { 'Accept': 'application/json' }
});
// Request interceptor - runs before every request
api.interceptors.request.use(
config => {
// Attach auth token from localStorage
const token = localStorage.getItem('authToken');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
},
error => Promise.reject(error)
);
// Response interceptor - runs after every response
api.interceptors.response.use(
response => response, // pass through successful responses
error => {
if (error.response?.status === 401) {
// Token expired - redirect to login
localStorage.removeItem('authToken');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
// Now use the instance - baseURL and interceptors apply automatically
api.get('/users').then(res => console.log(res.data));
api.post('/posts', { title: 'Hello' }).then(res => console.log(res.data));
| Feature | Axios | fetch() |
|---|---|---|
| Dependency | External library (~14KB) | Built-in |
| Auto JSON stringify | Yes | Manual |
| Auto JSON parse | Yes (response.data) | Manual (res.json()) |
| HTTP error rejection | Yes (4xx/5xx reject) | No - must check res.ok |
| Request cancellation | CancelToken / AbortController | AbortController |
| Interceptors | Built-in | Not built-in |
| Upload progress | onUploadProgress | Not built-in |
| Node.js support | Yes | Node 18+ only |
// async/await with Axios
async function createUser(userData) {
try {
const { data } = await axios.post('/api/users', userData);
console.log('New user ID:', data.id);
return data;
} catch (error) {
const msg = error.response?.data?.message || error.message;
console.error('Create user failed:', msg);
throw error;
}
}
// File upload with progress tracking
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
try {
const { data } = await axios.post('/api/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
document.getElementById('progress').style.width = `${percent}%`;
document.getElementById('progress-text').textContent = `${percent}%`;
}
});
console.log('Uploaded:', data.url);
} catch (err) {
console.error('Upload failed:', err.message);
}
}
Create one instance for one API boundary with its base URL, timeout, and stable headers. Keep environment-specific origins in build or deployment configuration. Use the params option for URL query values and data for a JSON request body; this makes the request intent visible and lets Axios perform serialization consistently.
A timeout limits how long the client waits, but it does not prove the server stopped processing a write. Apply the same idempotency rules used for fetch. Use an AbortController signal when the user action or component lifecycle should explicitly cancel the request.
A request interceptor can attach a current authorization credential or correlation header. A response interceptor can normalize a narrow transport concern or coordinate one token-refresh mechanism. Keep business-specific redirects, notifications, and data mutations near the feature that owns them; global interceptors otherwise create surprising side effects for unrelated calls.
Interceptors registered dynamically return an ID that can be ejected. This matters in tests, hot reload, and component-scoped setup, where repeated registration can run the same interceptor multiple times. Avoid retry loops by marking a refresh attempt and excluding the refresh request itself.
When Axios rejects, error.response indicates that the server replied outside the accepted status range, error.request indicates that a request was made without a usable response, and a setup error has neither. Cancellation is a separate expected case. Classify these branches before choosing a message or retry.
Axios normally rejects non-2xx statuses, unlike fetch, but validateStatus can change that policy. Document any custom policy on the instance because it changes which branch consumers must handle. Do not log the complete configuration object when it may contain credentials or sensitive request data.
Try this next
0 of 2 completed
Explore 500+ free tutorials across 20+ languages and frameworks.