Skip to content
Axios Interceptors & Error Handling — Complete Guide with Examples

Axios Interceptors & Error Handling — Complete Guide with Examples

DodaTech Updated Jun 6, 2026 17 min read

Axios interceptors let you run code before every request or after every response — perfect for adding auth tokens, logging, and centralized error handling without repeating yourself in every API call.

What You’ll Learn

By the end of this tutorial, you’ll add auth tokens automatically to every request with request interceptors, transform and log responses with response interceptors, handle network errors, timeouts, 4xx and 5xx errors properly, implement retry logic with exponential backoff, and build a token refresh flow that handles 401 errors seamlessly.

Prerequisites: You should understand basic Axios requests and request configuration. If you can make a GET request and use a config object, you’re ready.

Why Interceptors Matter

Imagine you have an app with 20 API calls. Every one of them needs an auth token in the header. Without interceptors, you’d write headers: { Authorization: 'Bearer ' + token } in all 20 places. When the token changes, you’d update 20 files.

Now imagine some API calls fail with a 401 (unauthorized). You need to refresh the token and retry the request. Doing this in all 20 places would be a nightmare.

Interceptors solve this. They’re like a security checkpoint that every request passes through. You set up the checkpoint once, and every request — past and future — goes through it automatically.

Real-world use: Durga Antivirus Pro uses Axios interceptors to attach license verification tokens to every request to its cloud scanning API. When a token expires, the response interceptor detects the 401, refreshes the token, retries the original request — all without the calling code knowing anything happened.

Where This Fits in Your Learning Path

    flowchart LR
    A["Axios Getting Started"] --> B["Requests & Config"]
    B --> C["**Interceptors & Error Handling**"]
    C --> D["Axios Advanced"]
    D --> E["Full-Stack API Apps"]
    style C fill:#f97316,stroke:#c2410c,color:#fff
    style A fill:#e5e7eb,stroke:#9ca3af,color:#374151
    style E fill:#22c55e,stroke:#16a34a,color:#22c55e
  

What is an Interceptor?

Think of an interceptor like a loyal assistant who handles things for you before and after every API call.

  • Before the request goes out (request interceptor): Your assistant checks “do we have an auth token? add it to the header.” Or “let’s log what we’re about to send.”
  • After the response comes back (response interceptor): Your assistant checks “did we get an error? let’s handle it.” Or “let’s transform the data before passing it to the app.”

You set up the assistant once, and it works for every request.


Request Interceptors

A request interceptor runs before the request is sent. Use it to modify the config — most commonly to add auth tokens.

Adding Auth Tokens Automatically

import axios from 'axios'

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

api.interceptors.request.use(
  (config) => {
    // Get the token from wherever you store it
    const token = localStorage.getItem('authToken')

    // If we have a token, add it to the Authorization header
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }

    // MUST return the config — otherwise the request stops here
    return config
  },
  (error) => {
    // If something went wrong setting up the request, reject it
    return Promise.reject(error)
  }
)

What’s happening step by step:

  1. Every request through api enters the interceptor
  2. The interceptor reads the token from localStorage
  3. If a token exists, it sets Authorization: Bearer <token> on the request headers
  4. The modified config is returned, and the request continues

Why must you return config? Because the interceptor is a chain. If you don’t return config, the next interceptor in the chain receives undefined, and the request never gets sent.

Logging Requests

api.interceptors.request.use((config) => {
  console.log(`[${config.method?.toUpperCase()}] ${config.url}`)
  return config
})

Every request now gets logged. When debugging, you can see exactly what your app is sending.


Response Interceptors

A response interceptor runs after the response arrives but before your .then() or await handlers receive it.

Transforming Response Data

api.interceptors.response.use(
  (response) => {
    // Unwrap the .data property so callers get data directly
    return response.data
  },
  (error) => {
    // Pass errors through
    return Promise.reject(error)
  }
)

After this interceptor, instead of writing:

const res = await api.get('/users')
console.log(res.data)  // need .data

You can write:

const users = await api.get('/users')
console.log(users)  // no .data needed

Normalizing Errors

api.interceptors.response.use(
  (response) => response,
  (error) => {
    // Transform the error into a consistent format
    const normalized = {
      message: error.response?.data?.message || error.message,
      status: error.response?.status,
      code: error.code,
      data: error.response?.data,
    }
    return Promise.reject(normalized)
  }
)

Now every error in your app has the same shape: { message, status, code, data }. No more guessing whether the error is from the server, the network, or the code.


Interceptor Order and Lifecycle

Registration vs Execution Order

This is a common source of confusion. Let’s make it clear:

// Registration order: A → B → C
api.interceptors.request.use(A)  // registered first
api.interceptors.request.use(B)  // registered second
api.interceptors.request.use(C)  // registered third

// ✅ Request interceptors: C → B → A (LIFO — last in, first out)
// ✅ Response interceptors: A → B → C (FIFO — first in, first out)

Why do request interceptors run in reverse? Think of middleware. The last interceptor to register is the closest to the actual request, so it runs first. This matches how middleware patterns work in Express and other frameworks.

Removing an Interceptor

Each interceptor returns an ID you can use to remove it:

const id = api.interceptors.request.use((config) => {
  config.headers['X-Debug'] = 'true'
  return config
})

// Later: remove it
api.interceptors.request.eject(id)

Clearing All Interceptors

api.interceptors.request.clear()
api.interceptors.response.clear()

Error Handling Strategies

Different errors need different responses. Here’s how to handle each type.

Network Errors

When the browser can’t reach the server (no internet, DNS failure):

try {
  const res = await axios.get('/api/data')
} catch (error) {
  if (error.code === 'ERR_NETWORK') {
    showToast('Network error — check your internet connection')
  }
}

Timeout Errors

When the server took too long to respond:

try {
  const res = await axios.get('/api/data', { timeout: 5000 })
} catch (error) {
  if (error.code === 'ECONNABORTED') {
    showToast('Request timed out — the server might be overloaded')
  }
}

4xx Client Errors

The server received the request but refuses to process it:

try {
  const res = await axios.get('/api/users/999')
} catch (error) {
  if (error.response) {
    switch (error.response.status) {
      case 400: handleBadRequest(error.response.data); break  // Bad input
      case 401: handleUnauthorized(); break                     // Not logged in
      case 403: handleForbidden(); break                        // Not allowed
      case 404: handleNotFound(); break                         // Doesn't exist
      case 422: handleValidation(error.response.data); break    // Invalid data
      case 429: handleRateLimit(error.response.headers['retry-after']); break
      default:  handleOther(error.response)
    }
  }
}

5xx Server Errors

The server crashed or is having issues:

api.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status >= 500) {
      // Log server errors for debugging
      console.error(`Server error (${error.response.status}) on ${error.config.url}`)

      // Optionally send to your error tracking service
      reportError(error)
    }
    return Promise.reject(error)
  }
)

Retry Logic

Sometimes requests fail due to temporary issues — a server restart, a network blip. Retrying after a short delay can succeed.

Using axios-retry

npm install axios-retry
import axios from 'axios'
import axiosRetry from 'axios-retry'

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

axiosRetry(api, {
  retries: 3,                       // Number of retry attempts
  retryDelay: (retryCount) => {
    return retryCount * 1000        // 1s, 2s, 3s — linear backoff
  },
  retryCondition: (error) => {
    // Retry on network errors and 5xx responses
    return axiosRetry.isNetworkError(error) ||
           axiosRetry.isRetryableError(error) ||
           error.response?.status >= 500
  },
  onRetry: (retryCount, error, requestConfig) => {
    console.log(`Retry ${retryCount} for ${requestConfig.url}`)
  },
})

Custom Retry with Exponential Backoff

Without any library, you can build retry into a response interceptor:

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

api.interceptors.response.use(null, async (error) => {
  const config = error.config

  // Don't retry more than 3 times
  if (!config || config._retryCount >= 3) {
    return Promise.reject(error)
  }

  // Increment retry counter
  config._retryCount = (config._retryCount || 0) + 1

  // Exponential backoff: 1s, 2s, 4s (capped at 10s)
  const delay = Math.min(1000 * Math.pow(2, config._retryCount - 1), 10000)
  console.log(`Retrying in ${delay}ms (attempt ${config._retryCount})`)

  // Wait for the delay
  await new Promise((resolve) => setTimeout(resolve, delay))

  // Retry the request
  return api(config)
})

What is exponential backoff? Instead of waiting the same time between retries, you double the wait each time: 1 second, then 2 seconds, then 4 seconds. This prevents hammering a struggling server.


Token Refresh Flow (401 Handling)

This is the most practical production use of interceptors. When a user’s access token expires, the API returns 401. The interceptor catches it, refreshes the token using a refresh token, and retries the original request — all without the user noticing.

import axios from 'axios'

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

let isRefreshing = false       // Are we currently refreshing?
let failedQueue = []           // Requests that arrived while refreshing

// Process the queue of waiting requests
const processQueue = (error, token = null) => {
  failedQueue.forEach((prom) => {
    if (error) prom.reject(error)
    else prom.resolve(token)
  })
  failedQueue = []
}

api.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config

    // If 401 and not already retrying
    if (error.response?.status === 401 && !originalRequest._retry) {
      if (isRefreshing) {
        // Another request is already refreshing the token.
        // Queue this request until the refresh completes.
        return new Promise((resolve, reject) => {
          failedQueue.push({ resolve, reject })
        }).then((token) => {
          originalRequest.headers.Authorization = `Bearer ${token}`
          return api(originalRequest)
        })
      }

      originalRequest._retry = true
      isRefreshing = true

      try {
        // Call the refresh endpoint
        const { data } = await axios.post('/auth/refresh', {
          refreshToken: localStorage.getItem('refreshToken'),
        })

        // Store the new token
        const newToken = data.accessToken
        localStorage.setItem('authToken', newToken)

        // Unblock queued requests with the new token
        processQueue(null, newToken)

        // Retry the original request
        originalRequest.headers.Authorization = `Bearer ${newToken}`
        return api(originalRequest)
      } catch (refreshError) {
        // Refresh failed — kick user to login
        processQueue(refreshError, null)
        window.location.href = '/login'
        return Promise.reject(refreshError)
      } finally {
        isRefreshing = false
      }
    }

    return Promise.reject(error)
  }
)

What’s happening here?

  1. A request goes out, server returns 401
  2. The interceptor catches it
  3. If another request is already refreshing the token, this one queues up
  4. If not, it calls /auth/refresh with the stored refresh token
  5. On success: stores the new token, retries the original request, unblocks queued requests
  6. On failure: redirects to login page

This pattern ensures that even if 10 requests fail with 401 simultaneously, only one refresh call is made, and all 10 are retried.


Common Mistakes Beginners Make

1. Not Returning config From Request Interceptors

// Wrong: hangs the request forever
api.interceptors.request.use((config) => {
  console.log('Request:', config.url)
  // Missing: return config
})

// Correct: always return the config
api.interceptors.request.use((config) => {
  console.log('Request:', config.url)
  return config
})

If you don’t return config, the request never reaches the server. The promise hangs indefinitely.

2. Modifying headers Without Checking They Exist

// Wrong: throws if config.headers is undefined
config.headers['X-Custom'] = 'value'

// Correct: ensure headers exists first
config.headers = config.headers || {}
config.headers['X-Custom'] = 'value'

3. Infinite Retry Loop

// Wrong: retries forever
api.interceptors.response.use(null, async (error) => {
  await new Promise(r => setTimeout(r, 1000))
  return api(error.config) // No retry limit!
})

// Correct: track retry count, stop after 3
error.config._retryCount = (error.config._retryCount || 0) + 1
if (error.config._retryCount > 3) return Promise.reject(error)

4. Not Using try/catch With Interceptor Errors

Interceptors run asynchronously. If a request interceptor throws, the request never goes out. Always wrap interceptor logic in try/catch, or use the error handler parameter.

5. Forgetting Interceptors Are Instance-Specific

// These interceptors only apply to `api`, not the default `axios`
const api = axios.create({ baseURL: '/api' })
api.interceptors.request.use(/* ... */)

// Without interceptors — no auth token added!
axios.get('/public-endpoint')

If you use axios.create(), interceptors on that instance only affect requests through that instance.


Practice Questions

  1. What’s the difference between a request interceptor and a response interceptor? A request interceptor runs before the request is sent (modifying config). A response interceptor runs after the response is received (modifying the response or handling errors).

  2. Why must request interceptors return the config object? Because interceptors form a chain. Each interceptor passes its result to the next. If you don’t return config, the next step receives undefined and the request never completes.

  3. In what order do request interceptors execute? LIFO (last in, first out) — the most recently registered interceptor runs first.

  4. What does the _retryCount property do in a retry interceptor? It tracks how many times a request has been retried. Check it before retrying to avoid infinite loops.

  5. What happens in the token refresh flow when multiple requests get 401 simultaneously? The first request triggers the refresh. Subsequent requests queue up. When the refresh completes, all queued requests are retried with the new token.

Challenge

Build an Axios instance with:

  • A request interceptor that reads a token from sessionStorage and attaches it as a header
  • A response interceptor that catches 401 errors, calls /api/auth/refresh, updates the token, and retries the original request
  • A retry mechanism for 5xx errors with exponential backoff (max 3 retries)
  • Logging that records every request method, URL, status, and duration

FAQ

Do interceptors run for all requests on the instance?

Yes. Interceptors registered on an instance run for every request through that instance. To skip a specific request, add a custom flag to the config and check it inside the interceptor.

Can I skip an interceptor for a specific request?

Yes. Add a custom property to the config and check it in the interceptor:
api.interceptors.request.use((config) => { if (config.skipAuth) return config // skip this request config.headers.Authorization = Bearer ${getToken()} return config })
api.get(’/public’, { skipAuth: true })

Can I use async/await inside an interceptor?

Yes. Return a promise (or use async/await) from the interceptor handler. For request interceptors, make sure to return the config. For response interceptors, return the response.

How do I remove an interceptor later?

Store the ID returned by .use() and call .eject(id). Or call .clear() to remove all interceptors from the instance.

What is the difference between interceptor error handler and try/catch?

The interceptor error handler (second argument to .use()) catches errors during request setup (like reading from localStorage). The try/catch in your calling code catches errors from the actual HTTP request. Both are needed.

Try It Yourself: API Dashboard

This interactive dashboard lets you experiment with interceptors, retries, and error handling in real time.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Axios API Dashboard</title>
  <script src="https://cdn.jsdelivr.net/npm/axios@1.7.0/dist/axios.min.js"></script>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: system-ui, -apple-system, sans-serif; background: #0f172a; color: #e2e8f0; padding: 2rem; }
    .container { max-width: 1200px; margin: 0 auto; }
    h1 { font-size: 1.75rem; margin-bottom: 0.25rem; }
    .subtitle { color: #94a3b8; margin-bottom: 1.5rem; }
    .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
    .card { background: #1e293b; border-radius: 0.75rem; padding: 1.5rem; border: 1px solid #334155; margin-bottom: 1rem; }
    .card h2 { font-size: 1.05rem; margin-bottom: 0.75rem; color: #38bdf8; }
    .flex { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
    label { color: #94a3b8; font-size: 0.85rem; }
    input, textarea, select { background: #0f172a; color: #e2e8f0; border: 1px solid #334155; border-radius: 0.375rem; padding: 0.5rem 0.625rem; font-family: monospace; font-size: 0.875rem; width: 100%; }
    select { width: auto; }
    .btn { background: #38bdf8; color: #0f172a; border: none; padding: 0.5rem 1.25rem; border-radius: 0.375rem; font-weight: 600; cursor: pointer; font-size: 0.875rem; }
    .btn:hover { background: #7dd3fc; }
    .btn-outline { background: transparent; color: #38bdf8; border: 1px solid #38bdf8; }
    .btn-outline:hover { background: #38bdf8; color: #0f172a; }
    .btn-danger { background: #ef4444; color: #fff; }
    .btn-danger:hover { background: #dc2626; }
    .btn-sm { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
    .output-box { background: #0f172a; border: 1px solid #334155; border-radius: 0.375rem; padding: 0.75rem; min-height: 60px; max-height: 200px; overflow: auto; font-family: monospace; font-size: 0.8rem; white-space: pre-wrap; word-break: break-all; }
    .output-box.success { border-color: #22c55e; }
    .output-box.error { border-color: #ef4444; color: #fca5a5; }
    .output-box.info { border-color: #3b82f6; }
    .log-entry { font-size: 0.75rem; padding: 0.25rem 0; border-bottom: 1px solid #1e293b; display: flex; gap: 0.5rem; }
    .log-time { color: #475569; min-width: 4rem; }
    .log-status { display: inline-block; padding: 0.0625rem 0.3125rem; border-radius: 0.125rem; font-weight: 700; font-size: 0.65rem; }
    .log-status.ok { background: #22c55e; color: #0f172a; }
    .log-status.err { background: #ef4444; color: #fff; }
    .log-status.info { background: #3b82f6; color: #fff; }
    .stat { color: #94a3b8; font-size: 0.8rem; margin-top: 0.25rem; }
    .token-display { background: #0f172a; border: 1px dashed #334155; border-radius: 0.25rem; padding: 0.5rem; font-family: monospace; font-size: 0.75rem; margin-top: 0.5rem; word-break: break-all; }
    @media (max-width: 768px) { .grid-2 { grid-template-columns: 1fr; } }
  </style>
</head>
<body>
<div class="container">
  <h1>API Dashboard</h1>
  <p class="subtitle">Interceptor logging, retry demo, error simulation, and token refresh</p>

  <div class="grid-2">
    <div class="card">
      <h2>Request</h2>
      <div class="flex">
        <select id="method" style="width:auto;">
          <option value="get">GET</option>
          <option value="post">POST</option>
        </select>
        <input type="text" id="url" value="https://jsonplaceholder.typicode.com/posts/1" style="flex:1;" />
      </div>
      <div class="flex" style="margin-top:0.75rem;">
        <button class="btn" onclick="sendRequest()">Send</button>
        <button class="btn btn-outline btn-sm" onclick="simulateError('network')">Network Error</button>
        <button class="btn btn-outline btn-sm" onclick="simulateError('timeout')">Timeout</button>
        <button class="btn btn-outline btn-sm" onclick="simulateError('401')">401</button>
        <button class="btn btn-outline btn-sm" onclick="simulateError('500')">500</button>
      </div>
      <div class="flex" style="margin-top:0.75rem;">
        <label>Retries: <input type="number" id="retries" value="0" min="0" max="5" style="width:60px;display:inline;" /></label>
        <label>Delay: <input type="number" id="retryDelay" value="500" min="100" step="100" style="width:80px;display:inline;" /> ms</label>
      </div>
    </div>

    <div class="card">
      <h2>Interceptor Log</h2>
      <div class="output-box" id="interceptorLog" style="max-height:220px;">No interceptor activity yet</div>
      <div class="flex" style="margin-top:0.5rem;">
        <label><input type="checkbox" id="logEnabled" checked /> Enable logging</label>
        <button class="btn btn-outline btn-sm" onclick="clearLog()">Clear Log</button>
      </div>
    </div>
  </div>

  <div class="grid-2">
    <div class="card">
      <h2>Response</h2>
      <div class="output-box" id="responseOutput">Send a request</div>
      <div class="stat" id="responseStat"></div>
    </div>

    <div class="card">
      <h2>Token Refresh Demo</h2>
      <p style="color:#94a3b8;font-size:0.85rem;margin-bottom:0.5rem;">Simulates 401 → refresh token → retry flow.</p>
      <div class="flex">
        <span class="stat">Access Token:</span>
        <span id="accessTokenDisplay" style="color:#22c55e;font-family:monospace;font-size:0.75rem;">valid_token_abc</span>
        <button class="btn btn-sm btn-outline" onclick="simulateTokenRefresh()">401 + Refresh</button>
        <button class="btn btn-sm btn-outline" onclick="revokeToken()">Revoke Token</button>
      </div>
      <div class="token-display" id="tokenStatus">Token is valid. Ready.</div>
    </div>
  </div>
</div>

<script>
const api = axios.create({ baseURL: '', timeout: 10000 });

let accessToken = 'valid_token_abc';
let refreshToken = 'refresh_token_xyz';
let tokenValid = true;

api.interceptors.request.use((config) => {
  if (!document.getElementById('logEnabled').checked) return config;
  addLog('REQ', config.method.toUpperCase() + ' ' + config.url, 'info');
  if (accessToken && !config.skipAuth) {
    config.headers.Authorization = 'Bearer ' + accessToken;
  }
  return config;
});

api.interceptors.response.use(
  (response) => {
    if (document.getElementById('logEnabled').checked) {
      addLog('RES', response.status + ' ' + response.config.url, 'ok');
    }
    return response;
  },
  async (error) => {
    if (document.getElementById('logEnabled').checked) {
      const status = error.response ? error.response.status : 'NET';
      addLog('ERR', status + ' ' + error.config.url, 'err');
    }
    return Promise.reject(error);
  }
);

function addLog(type, msg, cls) {
  const log = document.getElementById('interceptorLog');
  const time = new Date().toLocaleTimeString();
  const entry = document.createElement('div');
  entry.className = 'log-entry';
  entry.innerHTML = '<span class="log-time">' + time + '</span><span class="log-status ' + cls + '">' + type + '</span><span>' + msg + '</span>';
  log.appendChild(entry);
  log.scrollTop = log.scrollHeight;
  if (log.children.length > 50) log.removeChild(log.firstChild);
}

function clearLog() {
  document.getElementById('interceptorLog').innerHTML = 'No interceptor activity yet';
}

async function sendRequest() {
  const method = document.getElementById('method').value;
  const url = document.getElementById('url').value.trim();
  const retries = parseInt(document.getElementById('retries').value) || 0;
  const retryDelay = parseInt(document.getElementById('retryDelay').value) || 500;
  if (!url) { setResponse('Enter a URL', 'error'); return; }
  setResponse('Sending...', '');
  let lastError;
  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      if (attempt > 0) addLog('RET', 'Retry ' + attempt + '/' + retries + ' for ' + url, 'info');
      const res = await api({ method, url, validateStatus: () => true });
      setResponse(JSON.stringify(res.data, null, 2), 'success');
      document.getElementById('responseStat').innerHTML = 'Status: ' + res.status + ' | Attempt: ' + (attempt + 1);
      return;
    } catch (err) {
      lastError = err;
      if (attempt < retries) {
        await new Promise(r => setTimeout(r, retryDelay));
      }
    }
  }
  setResponse('Failed after ' + (retries + 1) + ' attempts. Error: ' + lastError.message, 'error');
  document.getElementById('responseStat').innerHTML = 'Last error: ' + (lastError.code || lastError.message);
}

function setResponse(text, cls) {
  const el = document.getElementById('responseOutput');
  el.textContent = text;
  el.className = 'output-box ' + cls;
}

function simulateError(type) {
  if (type === 'network') {
    document.getElementById('url').value = 'https://nonexistent.domain.test/api';
  } else if (type === 'timeout') {
    document.getElementById('url').value = 'https://httpbin.org/delay/10';
    document.getElementById('retries').value = 0;
  } else if (type === '401') {
    document.getElementById('url').value = 'https://jsonplaceholder.typicode.com/posts/1';
    tokenValid = false;
    accessToken = 'expired_token';
  } else if (type === '500') {
    document.getElementById('url').value = 'https://httpbin.org/status/500';
  }
  sendRequest();
  if (type !== '401') { tokenValid = true; accessToken = 'valid_token_abc'; }
}

function simulateTokenRefresh() {
  addLog('AUTH', '401 detected — attempting token refresh...', 'info');
  document.getElementById('tokenStatus').textContent = 'Refreshing token...';
  document.getElementById('tokenStatus').style.color = '#f59e0b';
  setTimeout(() => {
    accessToken = 'refreshed_token_' + Date.now();
    tokenValid = true;
    document.getElementById('accessTokenDisplay').textContent = accessToken;
    document.getElementById('accessTokenDisplay').style.color = '#22c55e';
    document.getElementById('tokenStatus').textContent = 'Token refreshed! Original request retried with new token.';
    document.getElementById('tokenStatus').style.color = '#22c55e';
    addLog('AUTH', 'Token refreshed successfully: ' + accessToken, 'ok');
  }, 1000);
}

function revokeToken() {
  accessToken = 'expired_token_xxx';
  tokenValid = false;
  document.getElementById('accessTokenDisplay').textContent = accessToken + ' (EXPIRED)';
  document.getElementById('accessTokenDisplay').style.color = '#ef4444';
  document.getElementById('tokenStatus').textContent = 'Token revoked. Next 401 will trigger refresh.';
  document.getElementById('tokenStatus').style.color = '#ef4444';
}
</script>
</body>
</html>

Key Features

  • Interceptor log — every request, response, and error is logged with timestamps
  • Error simulation — network error, timeout, 401, 500 with one click
  • Retry configurator — set retry count and delay between attempts
  • Token refresh demo — simulate 401 → refresh token → retry flow with visual feedback

What’s Next

TutorialWhat You’ll Learn
Axios AdvancedFile uploads, concurrency, caching, testing, production patterns

Related topics: Axios Requests & Config, JavaScript Promises, JWT Authentication.

What’s Next

Congratulations on completing this Axios Interceptors Errors tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro