DEV Community

Cover image for Axios vs Fetch: A Practical Guide to Error Handling, Interceptors & Retry Strategies
Sarvesh
Sarvesh

Posted on • Edited on

Axios vs Fetch: A Practical Guide to Error Handling, Interceptors & Retry Strategies

Summary:
Choosing between Axios and Fetch isn't just about preference — it can impact your error handling flow, maintainability, and even user experience. In this guide, we’ll compare how both handle errors, allow request interception, and implement retry strategies, with practical code examples.

Introduction

Both fetch and axios are used to make HTTP requests in JavaScript. While fetch is native and minimal, axios comes loaded with features. But when building production-grade applications, especially SPAs or SSR frameworks like Next.js, these differences matter — a lot.

Error Handling

Fetch

fetch('/api/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error('Fetch error:', error));

Enter fullscreen mode Exit fullscreen mode
  • Fetch does not throw on HTTP errors (like 404 or 500).
  • You must manually check response.ok or status codes.

Axios

axios.get('/api/data')
  .then(response => console.log(response.data))
  .catch(error => {
    if (error.response) {
      // Server responded with a status outside 2xx
      console.error('Axios error:', error.response.status);
    } else if (error.request) {
      // No response received
      console.error('No response:', error.request);
    } else {
      console.error('Error setting up request:', error.message);
    }
  });

Enter fullscreen mode Exit fullscreen mode
  • Axios automatically rejects the promise on HTTP errors.
  • Provides detailed info: error.response, error.request, etc.

Interceptors

Only Axios supports interceptors — a powerful feature for global request/response transformation.

Example: Add Auth Token

axios.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

Enter fullscreen mode Exit fullscreen mode

Use cases:

  • Add tokens to every request
  • Log or transform responses globally
  • Handle global error notifications

Fetch requires manual wrapping or abstraction to achieve similar behavior.

Retry Strategies

Axios with axios-retry:

import axios from 'axios';
import axiosRetry from 'axios-retry';

axiosRetry(axios, { retries: 3 });

axios.get('/unstable-endpoint')
  .then(res => console.log(res.data))
  .catch(err => console.error('Final failure:', err));

Enter fullscreen mode Exit fullscreen mode
  • Easy plug-and-play retries

  • Customize retry delay or conditions (e.g., network errors only)

Fetch Retry (Manual):

async function fetchWithRetry(url, options = {}, retries = 3) {
  try {
    const res = await fetch(url, options);
    if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
    return await res.json();
  } catch (error) {
    if (retries > 0) {
      return fetchWithRetry(url, options, retries - 1);
    } else {
      throw error;
    }
  }
}

Enter fullscreen mode Exit fullscreen mode
  • Requires writing custom logic

  • More flexible but more boilerplate

Real-World Use Case: Authenticated API with Retry & Global Error Logging

For a React app with authenticated APIs:

  • Axios interceptors inject the token into every request.

  • Retry logic handles flaky endpoints.

  • Centralized error handling logs errors and optionally shows a toast/alert.

Conclusion & Best Practices

Use Axios if:

  • You need interceptors or global config

  • You want automatic JSON handling and error rejection

  • You're dealing with complex apps (React, Vue, etc.)

Use Fetch if:

  • You want zero dependencies

  • Your app is small or needs custom lightweight requests

🔧 Best Practices:

  • Always handle HTTP errors explicitly

  • Use retry logic for network-prone endpoints

  • Centralize auth and error handling with interceptors

Final Thought:

The choice isn’t just about syntax — it’s about developer experience, error visibility, and long-term maintainability. Choose based on project needs, not just popularity.


👋 Connect with Me

Thanks for reading! If you found this post helpful or want to discuss similar topics in full stack development, feel free to connect or reach out:

🔗 LinkedIn: https://www.linkedin.com/in/sarvesh-sp/

🌐 Portfolio: https://sarveshsp.netlify.app/

📨 Email: sarveshsp@duck.com

Found this article useful? Consider sharing it with your network and following me for more in-depth technical content on Node.js, performance optimization, and full-stack development best practices.

Top comments (0)