How to Make Conditional Axios HTTP Requests

Conditional HTTP requests allow clients to optimize bandwidth and improve application speed by asking the server to return data only if it has changed since the last request. This article explains how to implement conditional requests in Axios using the If-None-Match (with ETags) and If-Modified-Since (with timestamps) headers, how to configure Axios to handle 304 Not Modified status codes, and how to combine these techniques with client-side caching.

Understanding Conditional Headers

Conditional requests rely on validators received from the initial server response:

If the resource has not changed, the server responds with an HTTP status 304 Not Modified and an empty body, signaling that the client should use its locally stored copy.

Sending Conditional Headers with Axios

You can pass conditional headers directly into the headers property of the Axios request configuration object.

import axios from 'axios';

async function fetchResource(url, cachedEtag, cachedLastModified) {
  try {
    const response = await axios.get(url, {
      headers: {
        ...(cachedEtag && { 'If-None-Match': cachedEtag }),
        ...(cachedLastModified && { 'If-Modified-Since': cachedLastModified }),
      },
      // Ensure Axios does not throw an error on 304 status codes
      validateStatus: (status) => (status >= 200 && status < 300) || status === 304,
    });

    if (response.status === 304) {
      console.log('Resource not modified. Using cached data.');
      return null; // Handle using local cache
    }

    // Capture headers for future conditional requests
    const newEtag = response.headers['etag'];
    const newLastModified = response.headers['last-modified'];

    return {
      data: response.data,
      etag: newEtag,
      lastModified: newLastModified,
    };
  } catch (error) {
    console.error('Request failed:', error);
    throw error;
  }
}

Handling 304 Status Codes with validateStatus

By default, Axios rejects promises for any HTTP status code outside the range of 2xx. Because 304 Not Modified falls under 3xx, Axios will throw an error unless configured otherwise.

To handle 304 responses gracefully, use the validateStatus option:

const response = await axios.get('https://api.example.com/data', {
  headers: {
    'If-None-Match': '"686897696a7c876b7e"',
  },
  validateStatus: (status) => (status >= 200 && status < 300) || status === 304,
});

Implementing a Basic In-Memory Cache

To fully leverage conditional requests, you should store the response body alongside its validator (ETag or Last-Modified):

const cache = new Map();

async function getCachedResource(url) {
  const cached = cache.get(url);
  const headers = {};

  if (cached?.etag) {
    headers['If-None-Match'] = cached.etag;
  }
  if (cached?.lastModified) {
    headers['If-Modified-Since'] = cached.lastModified;
  }

  const response = await axios.get(url, {
    headers,
    validateStatus: (status) => (status >= 200 && status < 300) || status === 304,
  });

  if (response.status === 304) {
    return cached.data;
  }

  const freshData = {
    data: response.data,
    etag: response.headers['etag'],
    lastModified: response.headers['last-modified'],
  };

  cache.set(url, freshData);
  return freshData.data;
}

Automating with Axios Interceptors

If you need conditional requests across your entire application, you can use Axios interceptors to automatically append stored ETags or timestamps before requests are sent, and update your cache when 200 OK or 304 Not Modified responses are received. Alternatively, existing ecosystem libraries such as axios-cache-interceptor offer built-in conditional request handling out of the box.