Mastering Axios - The Complete Guide to the Modern HTTP Client for JavaScript

Making and Handling API Requests with Axios

·

12 min read

Get Started with Axios

Axios has become the go-to HTTP client for web developers building modern JavaScript applications. Its promise-based interface, configurable defaults, interceptors and robust feature set make Axios flexible for diverse use cases.

In this comprehensive guide, you'll learn everything you need to use Axios like a pro. We'll cover making requests, handling responses, configuration, error handling, advanced usage with interceptors, and more.

By the end, you'll understand why Axios is so widely preferred and have the skills to integrate it into your web projects. Let's get started!

Introduction to Axios

Axios is a promise-based HTTP client for browser and Node.js. It provides an easy-to-use API for making HTTP requests and handling responses via Promises.

Some key features of Axios include:

  • Make GET, POST, PUT, DELETE requests

  • Configure defaults like baseURL, headers, timeout

  • Intercept requests and responses

  • Transform request and response data

  • Cancel requests

  • Automatic transforms for JSON data

  • Client-side support for protecting against XSRF

Axios was created by Matt Zabriskie in 2014 and is maintained by Matt and contributors on GitHub.

Axios works in both browser and Node.js environments. It supports older browsers and provides the polyfills needed for ES6 Promise support.

You can install Axios via npm:

npm install axios

Then import it in your JavaScript code:

// ES6 module import
import axios from 'axios';

// CommonJS require
const axios = require('axios');

Let's see a sample code :

// Get data
const response = await axios.get('/api/users');

// Post data  
const response = await axios.post('/api/users', {
  firstName: 'John'
});

// Error handling
try {
  await axios.get('/invalid');
} catch(error) {
  console.log(error);
}

// Global defaults
axios.defaults.baseURL = 'https://api.website.com';

Now let's look at making requests with Axios!

Making API Requests

The most common thing you'll do with Axios is make HTTP requests to APIs. Axios makes this very simple.

GET Request

To make a GET request, use the axios.get() method:

const response = await axios.get('/users');

Pass the URL of the API endpoint as the first argument. This returns a Promise which resolves with the response object.

Using async/await lets you write asynchronous code that reads like synchronous code. The response variable will contain the API response.

The response also contains the request object with info about the request.

POST Request

Making a POST request is just as easy. Use axios.post() and pass the URL, then the request data:

const response = await axios.post('/users', {
  firstName: 'John',
  lastName: 'Doe'
});

This makes a POST request to /users and sends the user data as JSON.

You can POST data in other formats like Form URL Encoded by setting the headers. But JSON is most common for APIs.

PUT, DELETE, PATCH

Axios provides shorthand methods for all HTTP verbs:

axios.get('/users');
axios.post('/users');
axios.put('/users/1234');
axios.delete('/users/1234');
axios.patch('/users/1234');
// etc

So making any kind of API request is straightforward with Axios.

Axios Global Defaults

Axios lets you configure default settings for all requests like BASE URL, headers, timeout, and more.

Set these global defaults when initializing Axios:

axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.timeout = 2500;

Now every request will use that base URL, auth header, and timeout unless overridden.

Axios Instance Defaults

You can also create axios instances with preconfigured defaults:

// Set config defaults when creating the instance
const api = axios.create({
  baseURL: 'https://api.example.com'
});

// Inherits 'https://api.example.com'
api.get('/users');

Instances are handy for organizing axios config - like for different APIs.

Configuring Requests

The axios request methods accept a config object which lets you specify per-request settings:

const response = await axios.get('/users', {
  params: {
    limit: 10 // query string params
  },
  timeout: 5000, // 5 second timeout
  headers: {
    'X-Custom-Header': 'foobar'
  }
});

These settings override the global defaults for one request.

Concurrency and Queuing

Axios allows multiple requests to run concurrently by default. The adapter determines the level of concurrency based on the environment.

You can use axios.all() to execute parallel requests:

const [usersRes, postsRes] = await axios.all([
  axios.get('/users'),
  axios.get('/posts')
]);

The responses arrive in the same order as requested.

Axios also provides axios.spread() to use the responses:

const [users, posts] = await axios.spread((usersRes, postsRes) => {
  return [usersRes.data, postsRes.data]
})

For queuing rather than concurrency, use a library like p-queue.

That covers the fundamentals of making API requests with Axios!

Here is a simple example of using Axios in React, Vue, and Angular:

// React
import axios from 'axios';

const fetchData = async () => {
  const response = await axios.get('/api/data');
  // update component state 
}

// Vue
import axios from 'axios';

async fetchData() {
  const response = await axios.get('/api/data');
  // update component data
}

// Angular
import { HttpClient } from '@angular/common/http';

constructor(private http: HttpClient) {}

getData() {
  return this.http.get('/api/data');  
}

Next, let's look at working with the responses.

Handling API Responses

Once an API request is made, we need to handle the response. Axios response objects contain all the data we need to process responses:

const {
  data,
  status,
  statusText,
  headers,
  config,
  request  
} = response;
  • data - Response payload as JSON

  • status - HTTP response code (200, 404, etc.)

  • statusText - HTTP status text ('OK', 'Not Found', etc.)

  • headers - Response headers object

  • config - Request configuration object

  • request - Actual HTTP request object

Let's look at some key aspects of handling responses.

Accessing Response Data

The response payload from APIs is typically JSON data. Axios parses this automatically and provides it on the data property:

// GET /users API route
const response = await axios.get('/users');

const users = response.data; // array of user objects

For binary data like images, Axios responds with a Buffer instead of parsing.

You can also parse data manually by setting responseType.

Status Codes

API responses contain useful status codes for success, errors, redirects etc.

Axios exposes the HTTP response code on response. status:

const response = await axios.get('/users');

if (response.status == 200) {
  // Success case  
} else if (response.status == 404) { 
  // Not Found error
}

Use status codes to check for success, identify errors, and implement error-handling flows.

Response Headers

Inspect the response headers for additional metadata via response. headers:

const response = await axios.get('/users');

console.log(response.headers['Content-Type']) // application/json

Headers contain info like content type, caching settings, tokens etc.

Config & Request

The response also contains the original request info that generated it:

const response = await axios.post('/users', {
  firstName: 'John' 
});

const requestData = response.config.data; // {firstName: 'John'}

The config contains request settings like URL, method, headers, and data.

Inspecting the request helps understand what produced the response.

// Basic Auth
axios.get('/profile', {
  auth: {
    username: 'john',
    password: 'secret'
  }
});

// Bearer token 
axios.defaults.headers.common['Authorization'] = 'Bearer <token>';

// OAuth
axios.get('/data', {
  headers: {
    Authorization: 'Bearer <access_token>' 
  }
});

Processing Responses

Once you have the response data, headers, status etc. you can process it as needed:

const response = await axios.get('/users');

// Show response data on page
showUsersOnPage(response.data); 

// Handle errors
if (response.status !== 200) {
  handleError(response);
}

// Extract Link header
const links = parseLinkHeader(response.headers.link);

Common tasks include displaying data, error handling, pagination, saving state responses, and more.

Configuring axios

Now that we've made requests and handled responses, let's look at some powerful configuration options provided by Axios.

Axios allows setting tons of options globally or per request:

  • Global axios defaults

  • Per-request config overrides

  • Axios instance defaults

  • Axios interceptors

Let's explore the options...

Global Axios defaults

Set global configurations using Axios.defaults:

axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.timeout = 2500;

Defaults are useful for:

  • BASE URL of API

  • Authentication headers

  • Default timeouts

  • Default headers like Accept, Content-Type etc.

Now these settings apply to every request.

Per-request config

Override the defaults by passing a config object when making the request:

// Defaults to https://api.example.com
axios.defaults.baseURL = 'https://api.example.com';

// Override baseURL for this request
const response = await axios.get('/users', {
  baseURL: 'http://localhost:3000'
});

Common overrides include:

  • baseURL - Alternate API endpoint

  • timeout - Request specific timeout

  • headers - Special headers for one request

  • responseType - Buffer, JSON, text etc.

Axios instances

Axios lets you create reusable instances with preconfigured defaults:

// Instance with common config
const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 3000
});

// Use it like default axios
api.get('/users') // https://api.example.com/users

Instances are great for:

  • Organizing axios config

  • Separate projects - like microservices

  • Different environments - dev vs prod

Just create an instance and use it like normal Axios.

Interceptors

Axios provides powerful interceptors for global pre and post-processing of requests & responses:

// Add auth token to all requests
axios.interceptors.request.use(config => {
  config.headers.common['Authorization'] = AUTH_TOKEN;
  return config;
});

// Log errors 
axios.interceptors.response.use(null, error => {
  console.log(error);
  return Promise.reject(error);
});

Interceptors are useful for:

  • Processing headers

  • Logging

  • Error handling

  • Transforming data

  • Authentication

  • Metrics

And much more!

That covers the major Axios configuration options.

// Environment-specific defaults
const api = axios.create({
  baseURL: process.env.API_URL  
});

// Client-specific defaults
const api = axios.create({
  headers: {
    'X-Client': '123'
  }   
});

// Dynamic headers
axios.interceptors.request.use(config => {
  config.headers.type = getFileType(); // dynamic
  return config;
});

Real-world examples:

  • Add global authentication header with interceptors:
// Set auth token for all requests  
axios.interceptors.request.use(config => {
  config.headers.common['Authorization'] = AUTH_TOKEN;
  return config;
});
  • Transform response data to custom format:
// Convert snake_case to camelCase
axios.interceptors.response.use(res => {
  res.data = convertToCamelCase(res.data); 
  return res;
});

Next, let's handle errors which are inevitable when working with HTTP requests.

Error Handling in Axios

When working with external APIs and networks, errors are bound to happen. Axios makes handling errors straightforward.

The cleanest way is using async/await and standard try/catch:

async function getUsers() {
  try {
    const response = await axios.get('/users');
    console.log(response.data);
  } catch (error) {
    console.log(error);
    // handle error here   
  }
}

Alternatively, provide callbacks to .then() and .catch():

axios.get('/users')
  .then(response => {
    console.log(response.data);
  })
  .catch(error => {
    console.log(error.response); // full response
    console.log(error.request); // request object
  });

We have multiple options and rich data for handling errors.

Network Errors

Network errors and failures occur when the request doesn't complete:

{
  // Request failed to complete
  message: 'Network Error', 
  request: {...}, // Request object
  response: undefined // Response wasn't received
}

This could happen from network issues, server going down, CORS problems, timeouts etc.

The best practice is to retry failed requests 1-3 times with backoff.

Validation Errors

APIs may return 4xx errors like 400 Bad Request for invalid data:

{
  // Request completed but server responded with error    
  message: 'Validation Failed', 
  response: {
    status: 400,
    data: {
      errors: ['Invalid email']  
    }
  }
}

For validation errors, show the error message, but don't retry.

Server Errors

5xx errors indicate server-side issues:

{
  // Server had an internal error
  message: 'Internal Server Error',
  response: {
    status: 500,
    data: 'Something broke!'
  }
}

Retry 5xx errors 1-3 times before giving up. Monitor for spikes in server errors.

HTTP Status Codes

Axios rejects the Promise for HTTP errors so we can handle them in catch():

try {
  const response = await axios.get('/invalid-url');
} catch (error) {
  if (error.response) {
    // HTTP error from server
    if (error.response.status === 404) {
      alert('Page Not Found!');
    }
  } else {
    // Network error
    alert('Network Error');
  }
}

Using the status code, you can handle different scenarios properly.

Global Error Handling

For consistent error handling globally, use interceptors:

// Global app error handler
axios.interceptors.response.use(null, error => {
  if (error.response) {
    // HTTP client errors 
    handleClientError(error);  
  } else {
    // Network errors
    handleNetworkError(error);
  }

  return Promise.reject(error);
});

function handleClientError(error) {
  // Handle 4xx-5xx errors
}

function handleNetworkError(error) {
  // Retry or notify user
}

This avoids duplicate error-handling logic throughout the app.

To sum-up :

// Jest example 
import axios from 'axios';
import { mockResponse } from './mocks';

jest.mock('axios');

test('fetch users', async () => {
  axios.get.mockResolvedValue(mockResponse);

  const response = await axios.get('/users');
  expect(response.data).toEqual(mockResponse.data);
});

That covers a variety of approaches for handling errors with Axios!

Advanced Usage

Axios has many advanced features for customizing requests, automation, transitions, and more. Let's look at some advanced usage next.

Transforming Data

Transform request data before sending, and response data before receiving:

// Transform request data
axios.interceptors.request.use(config => {
  config.data = transformRequest(config.data);
  return config;  
})

// Transform response data
axios.interceptors.response.use(res => {
  res.data = transformResponse(res.data);
  return res;
})

function transformRequest(data) {
  // transform...
  return data;
}

function transformResponse(data) {
  // transform... 
  return data;
}

This allows normalizing data to the format backends or frontends expect.

Cancellation

Cancel in-flight requests using request cancellation tokens:

const cancelToken = axios.CancelToken.source();

axios.get('/users', {
  cancelToken: cancelToken.token
});

// Cancel the request
cancelToken.cancel('Operation canceled by user');

Aborting requests is useful for typeahead search UIs, route changes, etc.

Axios Instances

Create custom axios instances for different use cases:

const api = axios.create({
  baseURL: '/api'
});

const longTimeoutApi = axios.create({
  timeout: 10000 // 10 seconds
});

// Override timeouts as needed
longTimeoutApi.get('/long-request');

Instances keep code modular. Just import the preconfigured instance for that scenario.

interceptors

Axios interceptors allow monitoring and transforming requests & responses:

// Log all requests
axios.interceptors.request.use(config => {
  console.log('Making request to', config.url);
  return config;
});

// Retry requests
axios.interceptors.response.use(null, error => {
  return retryRequest(error.config);
});

// Redirect on 401 errors 
axios.interceptors.response.use(null, error => {
  if (error.response.status === 401) {
    router.push('/login');
  }

  return Promise.reject(error); 
});

Interceptors are extremely powerful!

This covers some of the more advanced features provided by Axios.

Improving Frontend Performance

When using Axios in the browser, there are additional considerations for performance best practices.

Here are some tips for optimizing requests for fast, responsive web apps:

  • Debounce/throttle rapid requests - avoid firing many at once

  • Avoid giant responses - paginate and limit response size

  • Enable HTTP caching with headers like Cache-Control

  • Preload requests early with

  • Use conditional requests with If-Modified-Since

  • Load non-critical resources lazily/on demand

  • Minify response JSON

  • Compress larger responses with gzip via Accept-Encoding

  • Use Service Workers to cache requests

  • Prioritize visible/critical resources

  • Set timeouts to prevent long delays

Following performance best practices will ensure using Axios doesn't negatively impact web app speed and user experience.

Lets conclude this with final example :

// Mocking
import mockAdapter from 'axios-mock-adapter';
const mock = new mockAdapter(axios);

// Caching
import cacheAdapter from 'axios-extensions';

// Error handling 
import { axiosRetry } from 'axios-retry';

// Upload progress
import { axiosProgress } from 'axios-progress-bar';

Conclusion

And that wraps up this comprehensive guide to Axios!

We covered making requests, handling responses, configuration, error handling, and advanced usage of this awesome library.

You should now have a deep understanding of how to integrate Axios into your apps and harness its full power.

The key takeaways are:

  • Easily make requests with GET, POST, PUT, DELETE and more

  • Use interceptors for request and response transformation

  • Handle errors gracefully with built-in tools

  • Configure globally or per request

  • Leverage response data, headers, status codes

  • Optimize performance best practices

  • Understand alternatives like Fetch API and jQuery

Axios provides an amazing developer experience for working with HTTP in JavaScript. I hope you found this guide useful! Let me know if you have any other questions.

Happy coding!

Did you find this article valuable?

Support Mikey by becoming a sponsor. Any amount is appreciated!