Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to use axios in Node.js

Making HTTP requests in Node.js applications often requires more features than the native fetch API provides, such as automatic JSON transformation, request cancellation, and interceptors. With over 10 years of experience building Node.js applications since 2014 and as the creator of CoreUI, a widely used open-source UI library, I’ve used axios in countless production backends and services. The most powerful and flexible approach is to use axios, a promise-based HTTP client that provides a rich feature set with a clean API. This method offers automatic JSON parsing, request and response interceptors, and built-in support for timeouts and error handling.

Use axios to make HTTP requests in Node.js with automatic JSON handling, interceptors, and comprehensive error management.

const axios = require('axios')

const response = await axios.get('https://api.example.com/users')
console.log(response.data)

This simple example makes a GET request and automatically parses the JSON response into the data property. Axios returns a promise that resolves to a response object containing data, status, headers, and other useful properties. Unlike native fetch, axios automatically handles JSON parsing and throws errors for non-2xx status codes, making error handling more straightforward.

Installing and Basic Configuration

const axios = require('axios')

const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json'
  }
})

const getUsers = async () => {
  try {
    const response = await api.get('/users')
    return response.data
  } catch (error) {
    console.error('Error fetching users:', error.message)
    throw error
  }
}

Creating an axios instance with axios.create() allows you to set default configuration that applies to all requests made with that instance. The baseURL property sets the base URL for all requests, so you only need to specify the path. The timeout property automatically cancels requests that take too long. Default headers apply to all requests unless overridden. This pattern is ideal for applications that make many requests to the same API.

Making POST Requests

const axios = require('axios')

async function createUser(userData) {
  try {
    const response = await axios.post('https://api.example.com/users', {
      name: userData.name,
      email: userData.email,
      age: userData.age
    }, {
      headers: {
        'Authorization': 'Bearer token123',
        'Content-Type': 'application/json'
      }
    })

    console.log('User created:', response.data)
    return response.data
  } catch (error) {
    if (error.response) {
      console.error('Server error:', error.response.status, error.response.data)
    } else if (error.request) {
      console.error('No response received:', error.request)
    } else {
      console.error('Request error:', error.message)
    }
    throw error
  }
}

const newUser = await createUser({
  name: 'John Doe',
  email: '[email protected]',
  age: 30
})

POST requests send data to the server using the second argument for the request body and the third argument for configuration options. Axios automatically serializes JavaScript objects to JSON when the Content-Type is application/json. The error handling demonstrates the three types of errors: error.response means the server responded with an error status, error.request means no response was received, and neither property indicates a request setup error.

Using Request and Response Interceptors

const axios = require('axios')

const api = axios.create({
  baseURL: 'https://api.example.com'
})

api.interceptors.request.use(
  (config) => {
    const token = getAuthToken()
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    console.log('Request:', config.method.toUpperCase(), config.url)
    return config
  },
  (error) => {
    console.error('Request error:', error)
    return Promise.reject(error)
  }
)

api.interceptors.response.use(
  (response) => {
    console.log('Response:', response.status, response.config.url)
    return response
  },
  (error) => {
    if (error.response?.status === 401) {
      console.log('Unauthorized, refreshing token...')
      return refreshTokenAndRetry(error.config)
    }
    return Promise.reject(error)
  }
)

function getAuthToken() {
  return 'your-auth-token'
}

async function refreshTokenAndRetry(config) {
  const newToken = await refreshAuthToken()
  config.headers.Authorization = `Bearer ${newToken}`
  return api.request(config)
}

Interceptors allow you to transform requests before they are sent and responses before they reach your code. Request interceptors are useful for adding authentication tokens, logging, or modifying headers. Response interceptors can handle global error cases like expired tokens, transform response data, or implement retry logic. The interceptor functions receive the config or response object and must return it or a promise that resolves to it.

Handling Multiple Concurrent Requests

const axios = require('axios')

async function fetchAllData() {
  try {
    const [users, posts, comments] = await Promise.all([
      axios.get('https://api.example.com/users'),
      axios.get('https://api.example.com/posts'),
      axios.get('https://api.example.com/comments')
    ])

    return {
      users: users.data,
      posts: posts.data,
      comments: comments.data
    }
  } catch (error) {
    console.error('Error fetching data:', error.message)
    throw error
  }
}

const allData = await fetchAllData()
console.log('Users:', allData.users.length)
console.log('Posts:', allData.posts.length)
console.log('Comments:', allData.comments.length)

Making multiple requests in parallel using Promise.all() significantly improves performance compared to sequential requests. Each axios call returns a promise, and Promise.all() waits for all promises to resolve before continuing. If any request fails, the entire operation fails and jumps to the catch block. This pattern is ideal when you need data from multiple endpoints before proceeding.

Implementing Request Cancellation

const axios = require('axios')

async function fetchWithCancellation() {
  const controller = new AbortController()

  setTimeout(() => {
    console.log('Cancelling request...')
    controller.abort()
  }, 2000)

  try {
    const response = await axios.get('https://api.example.com/slow-endpoint', {
      signal: controller.signal
    })
    return response.data
  } catch (error) {
    if (axios.isCancel(error)) {
      console.log('Request cancelled:', error.message)
    } else {
      console.error('Request error:', error.message)
    }
    throw error
  }
}

fetchWithCancellation()

Request cancellation is useful for preventing unnecessary network usage and handling situations where requests become irrelevant before completing. The AbortController provides a signal that can be passed to axios requests. Calling abort() cancels the request, and axios throws a cancellation error that you can detect with axios.isCancel(). This pattern is essential for search-as-you-type features or when users navigate away before requests complete.

Creating a Retry Mechanism

const axios = require('axios')

async function axiosRetry(config, maxRetries = 3, delay = 1000) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await axios(config)
    } catch (error) {
      const isLastAttempt = i === maxRetries - 1
      const shouldRetry = error.response?.status >= 500 || !error.response

      if (isLastAttempt || !shouldRetry) {
        throw error
      }

      console.log(`Retry attempt ${i + 1} after ${delay}ms`)
      await new Promise(resolve => setTimeout(resolve, delay * (i + 1)))
    }
  }
}

async function fetchDataWithRetry() {
  try {
    const response = await axiosRetry({
      method: 'get',
      url: 'https://api.example.com/unreliable-endpoint'
    }, 3, 1000)
    return response.data
  } catch (error) {
    console.error('Failed after retries:', error.message)
    throw error
  }
}

const data = await fetchDataWithRetry()

A retry mechanism automatically retries failed requests, improving reliability when dealing with flaky networks or temporarily unavailable servers. This implementation retries requests up to a maximum number of times with exponential backoff between attempts. Only server errors (5xx) and network errors are retried, as client errors (4xx) typically indicate a problem with the request itself. The delay increases with each attempt to avoid overwhelming the server.

Uploading Files with Progress Tracking

const axios = require('axios')
const FormData = require('form-data')
const fs = require('fs')

async function uploadFile(filePath) {
  const form = new FormData()
  form.append('file', fs.createReadStream(filePath))
  form.append('description', 'File upload example')

  try {
    const response = await axios.post('https://api.example.com/upload', form, {
      headers: {
        ...form.getHeaders()
      },
      maxContentLength: Infinity,
      maxBodyLength: Infinity,
      onUploadProgress: (progressEvent) => {
        const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
        console.log(`Upload progress: ${percentCompleted}%`)
      }
    })

    console.log('File uploaded successfully:', response.data)
    return response.data
  } catch (error) {
    console.error('Upload failed:', error.message)
    throw error
  }
}

uploadFile('./document.pdf')

File uploads require creating a FormData object and appending the file stream. The form.getHeaders() method generates the correct Content-Type header with the boundary for multipart/form-data. The onUploadProgress callback provides real-time progress updates, useful for displaying upload progress bars. Setting maxContentLength and maxBodyLength to Infinity removes size limits, allowing large file uploads.

Best Practice Note

Axios provides significant advantages over native fetch for complex Node.js applications, including automatic JSON transformation, interceptors for centralized request/response handling, and better error handling. For simpler use cases where these features are not needed, native fetch may be sufficient. Check out how to use fetch in Node.js for a lightweight alternative. This is the same approach we use in CoreUI backend services, choosing axios for feature-rich API integrations and fetch for simple, dependency-free requests.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team