axios-cache-adapter vs axios-extensions vs axios-retry
Enhancing Axios Reliability and Performance
axios-cache-adapteraxios-extensionsaxios-retrySimilar Packages:

Enhancing Axios Reliability and Performance

axios-cache-adapter, axios-extensions, and axios-retry are utilities designed to extend the capabilities of the Axios HTTP client. axios-cache-adapter focuses on caching responses to reduce network load. axios-extensions provides caching along with throttle and debounce capabilities for adapters. axios-retry specializes in automatically retrying failed requests based on configurable conditions. Together, they address common production needs like stability, performance, and resource management.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
axios-cache-adapter0722-596 years agoMIT
axios-extensions0838225 kB155 months agoMIT
axios-retry02,01333.6 kB582 years agoApache-2.0

Axios Reliability Tools: Cache, Retry, and Throttle Compared

When building robust frontend applications, handling network instability and redundant requests is critical. axios-cache-adapter, axios-extensions, and axios-retry each solve specific parts of this puzzle. While they all extend Axios, they operate at different layers and serve distinct purposes. Let's break down how they work and when to use them.

🗄️ Caching Strategies: Adapter vs Interceptor

Caching reduces server load and speeds up UI responses. The implementation differs significantly between these packages.

axios-cache-adapter wraps the Axios instance to intercept requests and store responses.

  • It respects HTTP Cache-Control headers by default.
  • Works by replacing the internal adapter.
// axios-cache-adapter
import axios from 'axios';
import { setupCache } from 'axios-cache-adapter';

const axios = setupCache(axios, {
  maxAge: 15 * 60 * 1000 // 15 minutes
});

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

axios-extensions enhances the adapter specifically for caching and throttling.

  • You must manually pass the underlying adapter to the enhancer.
  • Gives fine-grained control over which adapter is enhanced.
// axios-extensions
import axios from 'axios';
import { cacheAdapterEnhancer, getAdapter } from 'axios-extensions';

const axios = axios.create({
  adapter: cacheAdapterEnhancer(getAdapter(['https', 'http']), true)
});

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

axios-retry does not provide caching.

  • It focuses solely on re-executing failed requests.
  • Using it for caching would be incorrect; pair it with a cache package if needed.
// axios-retry (No caching feature)
import axios from 'axios';
import axiosRetry from 'axios-retry';

axiosRetry(axios, { retries: 3 });
// This will retry failures, not cache successes

🔄 Retry Logic: Handling Transient Failures

Network flakiness happens. Retry logic ensures temporary errors don't break the user experience.

axios-retry is the dedicated solution for this.

  • Configurable retry count and delay.
  • Can define custom conditions for when to retry (e.g., only on 500 errors).
// axios-retry
import axiosRetry from 'axios-retry';

axiosRetry(axios, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => {
    return axiosRetry.isNetworkOrIdempotentRequestError(error);
  }
});

axios-cache-adapter does not handle retries.

  • If a request fails, it does not automatically attempt again.
  • You would need to combine it with axios-retry for full resilience.
// axios-cache-adapter (No retry feature)
// Requires external logic or combining with axios-retry
const axios = setupCache(axios);
// No built-in retry configuration available

axios-extensions does not handle retries.

  • Focuses on rate limiting and caching.
  • Like cache-adapter, it requires pairing with axios-retry for failure handling.
// axios-extensions (No retry feature)
// Focuses on throttle/cache
const axios = axios.create({
  adapter: cacheAdapterEnhancer(getAdapter(['https', 'http']), true)
});
// No built-in retry configuration available

🛑 Rate Limiting: Throttle and Debounce

Preventing request floods is essential for search bars or real-time validation.

axios-extensions includes built-in throttle and debounce enhancers.

  • You can wrap the adapter to limit request frequency.
  • Ideal for input fields that trigger API calls.
// axios-extensions
import { throttleAdapterEnhancer, getAdapter } from 'axios-extensions';

const axios = axios.create({
  adapter: throttleAdapterEnhancer(getAdapter(['https', 'http']), 2000)
});

// Usage: Limits to one request every 2 seconds
const response = await axios.get('/api/search?q=term');

axios-cache-adapter does not support throttling.

  • It caches results but will still send every unique request to the server.
  • You would need a separate library for rate limiting.
// axios-cache-adapter (No throttle feature)
const axios = setupCache(axios);
// Will send every unique request immediately

axios-retry does not support throttling.

  • It only acts after a failure occurs.
  • Using it to limit success requests would be incorrect.
// axios-retry (No throttle feature)
axiosRetry(axios, { retries: 3 });
// Does not prevent initial request floods

⚠️ Axios Version Compatibility

This is a critical architectural decision point. Axios v1 introduced breaking changes to the adapter system.

axios-cache-adapter is primarily built for Axios v0.x.

  • The maintainer explicitly recommends axios-cache-interceptor for Axios v1+.
  • Using it with Axios v1 may require shims or fail entirely.
// axios-cache-adapter
// ⚠️ Warning: Check Axios version before installing
// Recommended for Axios v0.19+ only
const axios = setupCache(axios);

axios-extensions relies on adapter enhancement.

  • Compatibility with Axios v1 varies and may require testing.
  • Adapter internals changed, so enhancers might not hook in correctly without updates.
// axios-extensions
// ⚠️ Warning: Verify adapter compatibility with Axios v1
const axios = axios.create({
  adapter: cacheAdapterEnhancer(getAdapter(['https', 'http']), true)
});

axios-retry uses interceptors.

  • Interceptors are stable across Axios v0 and v1.
  • This makes it the safest bet for long-term maintenance.
// axios-retry
// ✅ Safe for Axios v0 and v1
axiosRetry(axios, { retries: 3 });

📊 Summary Table

Featureaxios-cache-adapteraxios-extensionsaxios-retry
Primary GoalCachingCache + ThrottleRetry Logic
MechanismAdapter WrapperAdapter EnhancerInterceptors
Axios v1 Ready❌ (Use interceptor)⚠️ (Verify)✅ Yes
Throttle/Debounce❌ No✅ Yes❌ No
Retry Logic❌ No❌ No✅ Yes
Cache Control✅ HTTP Headers✅ Custom❌ No

💡 The Big Picture

axios-retry is the most essential tool for production apps. Network failures are inevitable, and this package handles them gracefully with minimal setup. It works reliably across Axios versions because it uses interceptors.

axios-extensions is powerful for specific UI patterns like search inputs. If you need to stop users from firing 10 requests per second while typing, this is the tool. However, verify its compatibility with your Axios version.

axios-cache-adapter is largely legacy for new projects. If you are on Axios v1, do not use this. Instead, evaluate axios-cache-interceptor. Only use this package if you are maintaining an older codebase that cannot upgrade Axios.

Final Thought: For a modern stack, combine axios-retry for stability with a modern caching solution compatible with Axios v1. Use axios-extensions only if you specifically need its throttle enhancer and have verified it works in your environment.

How to Choose: axios-cache-adapter vs axios-extensions vs axios-retry

  • axios-cache-adapter:

    Choose axios-cache-adapter if you are locked into Axios v0.x and need a dedicated caching solution that respects HTTP cache headers. Be aware that for Axios v1, the maintainer recommends switching to axios-cache-interceptor instead. It is best suited for legacy projects where upgrading Axios is not an option.

  • axios-extensions:

    Choose axios-extensions if you need caching combined with throttle or debounce logic directly at the adapter level. This is useful for high-frequency requests like typeahead search where you want to limit outgoing calls while still caching results. Ensure compatibility with your Axios version before adopting.

  • axios-retry:

    Choose axios-retry if your primary goal is resilience against transient network failures or 5xx server errors. It is the industry standard for retry logic, works via interceptors, and maintains good compatibility across Axios versions. It is the safest choice for production stability.

README for axios-cache-adapter

:rocket: axios-cache-adapter Build Status codecov JavaScript Style Guide

Caching adapter for axios. Store request results in a configurable store to prevent unneeded network requests.

Adapted from superapi-cache by @stephanebachelier

Install

Using npm

npm install --save axios-cache-adapter

Or bower

bower install --save axios-cache-adapter

Or from a CDN like unpkg.com

<script type="text/javascript" src="https://unpkg.com/axios-cache-adapter"></script>

Usage

Important note: Only GET request results are cached by default. Executing a request using any method listed in exclude.methods will invalidate the cache for the given URL.

Instantiate adapter on its own

You can instantiate the axios-cache-adapter on its own using the setupCache() method and then attach the adapter manually to an instance of axios.

// Import dependencies
import axios from 'axios'
import { setupCache } from 'axios-cache-adapter'

// Create `axios-cache-adapter` instance
const cache = setupCache({
  maxAge: 15 * 60 * 1000
})

// Create `axios` instance passing the newly created `cache.adapter`
const api = axios.create({
  adapter: cache.adapter
})

// Send a GET request to some REST api
api({
  url: 'http://some-rest.api/url',
  method: 'get'
}).then(async (response) => {
  // Do something fantastic with response.data \o/
  console.log('Request response:', response)

  // Interacting with the store, see `localForage` API.
  const length = await cache.store.length()

  console.log('Cache store length:', length)
})

Instantiate axios with bound adapter

You can use the setup() method to get an instance of axios pre-configured with the axios-cache-adapter. This will remove axios as a direct dependency in your code.

// Import dependencies
import { setup } from 'axios-cache-adapter'

// Create `axios` instance with pre-configured `axios-cache-adapter` attached to it
const api = setup({
  // `axios` options
  baseURL: 'http://some-rest.api',

  // `axios-cache-adapter` options
  cache: {
    maxAge: 15 * 60 * 1000
  }
})

// Send a GET request to some REST api
api.get('/url').then(async (response) => {
  // Do something awesome with response.data \o/
  console.log('Request response:', response)

  // Interacting with the store, see `localForage` API.
  const length = await api.cache.length()

  console.log('Cache store length:', length)
})

Override instance config with per request options

After setting up axios-cache-adapter with a specific cache configuration you can override parts of that configuration on individual requests.

import { setup } from 'axios-cache-adapter'

const api = setup({
  baseURL: 'https://httpbin.org',

  cache: {
    maxAge: 15 * 60 * 1000
  }
})

// Use global instance config
api.get('/get').then((response) => {
  // Do something awesome with response
})

// Override `maxAge` and cache URLs with query parameters
api.get('/get?with=query', {
  cache: {
    maxAge: 2 * 60 * 1000, // 2 min instead of 15 min
    exclude: { query: false }
  }
})
  .then((response) => {
    // Do something beautiful ;)
  })

Note: Not all instance options can be overridden per request, see the API documentation at the end of this readme

Cache POST request results

You can allow axios-cache-adapter to cache the results of a request using (almost) any HTTP method by modifying the exclude.methods list.

import { setup } from 'axios-cache-adapter

const api = setup({
  baseURL: 'https://httpbin.org',

  cache: {
    exclude: {
      // Only exclude PUT, PATCH and DELETE methods from cache
      methods: ['put', 'patch', 'delete']
    }
  }
})

api.post('/post').then((response) => {
  // POST request has been cached \o/
})

Note: the request method is not used in the cache store key by default, therefore with the above setup, making a GET or POST request will respond with the same cache.

Use localforage as cache store

You can give a localforage instance to axios-cache-adapter which will be used to store cache data instead of the default in memory store.

Note: This only works client-side because localforage does not work in Node.js

import localforage from 'localforage'
import memoryDriver from 'localforage-memoryStorageDriver'
import { setup } from 'axios-cache-adapter'

// `async` wrapper to configure `localforage` and instantiate `axios` with `axios-cache-adapter`
async function configure () {
  // Register the custom `memoryDriver` to `localforage`
  await localforage.defineDriver(memoryDriver)

  // Create `localforage` instance
  const forageStore = localforage.createInstance({
    // List of drivers used
    driver: [
      localforage.INDEXEDDB,
      localforage.LOCALSTORAGE,
      memoryDriver._driver
    ],
    // Prefix all storage keys to prevent conflicts
    name: 'my-cache'
  })

  // Create `axios` instance with pre-configured `axios-cache-adapter` using a `localforage` store
  return setup({
    // `axios` options
    baseURL: 'http://some-rest.api',

    // `axios-cache-adapter` options
    cache: {
      maxAge: 15 * 60 * 1000,
      store: forageStore // Pass `localforage` store to `axios-cache-adapter`
    }
  })
}

configure().then(async (api) => {
  const response = await api.get('/url')

  // Display something beautiful with `response.data` ;)
})

Use redis as cache store

You can give a RedisStore instance to axios-cache-adapter which will be used to store cache data instead of the default in memory store.

Note: This only works server-side

const { setup, RedisStore } = require('axios-cache-adapter')
const redis = require('redis')

const client = redis.createClient({
  url: 'REDIS_URL',
})
const store = new RedisStore(client)
const api = setup({
  // `axios` options
  baseURL: 'http://some-rest.api',
  // `axios-cache-adapter` options
  cache: {
    maxAge: 15 * 60 * 1000,
    store // Pass `RedisStore` store to `axios-cache-adapter`
  }
})

const response = await api.get('/url')

Use Redis Default Store as Cache Store

You can give a RedisDefaultStore instance to axios-cache-adapter which will be used to store cache data in Redis using the default commands instead of hash commands.

Note: This only works server-side

const { setup, RedisDefaultStore } = require('axios-cache-adapter')
const redis = require('redis')

const client = redis.createClient({
  url: 'REDIS_URL',
})
const store = new RedisDefaultStore(client, {
  prefix: 'namespace_as_prefix' // optional
})
const api = setup({
  // `axios` options
  baseURL: 'http://some-rest.api',
  // `axios-cache-adapter` options
  cache: {
    maxAge: 15 * 60 * 1000,
    store // Pass `RedisDefaultStore` store to `axios-cache-adapter`
  }
})

const response = await api.get('/url')

Check if response is served from network or from cache

When a response is served from cache a custom response.request object is created with a fromCache boolean.

// Import dependencies
import assert from 'assert'
import { setup } from 'axios-cache-adapter'

// Create `axios` instance with pre-configured `axios-cache-adapter`
const api = setup({
  cache: {
    maxAge: 15 * 60 * 1000
  }
})

// Wrap code in an `async` function
async function exec () {
  // First request will be served from network
  const response = await api.get('http://some-rest.api/url')

  // `response.request` will contain the origin `axios` request object
  assert.ok(response.request.fromCache !== true)

  // Second request to same endpoint will be served from cache
  const anotherResponse = await api.get('http://some-rest.api/url')

  // `response.request` will contain `fromCache` boolean
  assert.ok(anotherResponse.request.fromCache === true)
}

// Execute our `async` wrapper
exec()

Read stale cache data on network error

You can tell axios-cache-adapter to read stale cache data when a network error occurs using the readOnError option.

readOnError can either be a Boolean telling cache adapter to attempt reading stale cache when any network error happens or a Function which receives the error and request objects and then returns a Boolean.

By default axios-cache-adapter clears stale cache data automatically, this would conflict with activating the readOnError option, so the clearOnStale option should be set to false.

import { setup } from 'axios-cache-adapter'

const api = setup({
  cache: {
    // Attempt reading stale cache data when response status is either 4xx or 5xx
    readOnError: (error, request) => {
      return error.response.status >= 400 && error.response.status < 600
    },
    // Deactivate `clearOnStale` option so that we can actually read stale cache data
    clearOnStale: false
  }
})

// Make a first successful request which will store the response in cache
api.get('https://httpbin.org/get').then(response => {
  // Response will not come from cache
  assert.ok(response.request.fromCache !== true)
})

// Let's say that the stored data has become stale (default 15min max age has passed)
// and we make the same request but it results in an internal server error (status=500)
api.get('https://httpbin.org/get').then(response => {
  // Response is served from cache
  assert.ok(response.request.fromCache === true)
  // We can check that it actually served stale cache data
  assert.ok(response.request.stale === true)
}).catch(err => {
  // Will not execute this because stale cache data was returned
  // If the attempt at reading stale cache fails, the network error will be thrown and this method executed
})

Note: Passing a function to readOnError is a smarter thing to do as you get to choose when a stale cache read should be attempted instead of doing it on all kind of errors

Invalidate cache entries

Using the default invalidation method, a cache entry will be invalidated if a request is made using one of the methods listed in exclude.methods.

async function defaultInvalidate (config, request) {
  const method = request.method.toLowerCase()

  if (config.exclude.methods.includes(method)) {
    await config.store.removeItem(config.uuid)
  }
}

You can customize how axios-cache-adapter invalidates stored cache entries by providing a custom invalidate function.

import { setup } from 'axios-cache-adapter'

// Create cached axios instance with custom invalidate method
const api = setup({
  cache: {
    // Invalidate only when a specific option is passed through config
    invalidate: async (config, request) => {
      if (request.clearCacheEntry) {
        await config.store.removeItem(config.uuid)
      }
    }
  }
})

// Make a request that will get stored into cache
api.get('https://httpbin.org/get').then(response => {
  assert.ok(response.request.fromCache !== true)
})

// Wait some time

// Make another request to same end point but force cache invalidation
api.get('https://httpbin.org/get', { clearCacheEntry: true }).then(response => {
  // Response should not come from cache
  assert.ok(response.request.fromCache !== true)
})

Use response headers to automatically set maxAge

When you set the readHeaders option to true, the adapter will try to read cache-control or expires headers to automatically set the maxAge option for the given request.

import assert from 'assert'
import { setup } from 'axios-cache-adapter'

const api = setup({
  cache: {
    // Tell adapter to attempt using response headers
    readHeaders: true,
    // For this example to work we disable query exclusion
    exclude: { query: false }
  }
})

// Make a request which will respond with header `cache-control: max-age=60`
api.get('https://httpbin.org/cache/60').then(response => {
  // Cached `response` will expire one minute later
})

// Make a request which responds with header `cache-control: no-cache`
api.get('https://httpbin.org/response-headers?cache-control=no-cache').then(response => {
  // Response will not come from cache
  assert.ok(response.request.fromCache !== true)

  // Check that query was excluded from cache
  assert.ok(response.request.excludedFromCache === true)
})

Note: For the cache-control header, only the max-age, no-cache and no-store values are interpreted.

API

setupCache(options)

Create a cache adapter instance. Takes an options object to configure how the cached requests will be handled, where they will be stored, etc.

Options

// Options passed to `setupCache()`.
{
  // {Number} Maximum time for storing each request in milliseconds,
  // defaults to 15 minutes when using `setup()`.
  maxAge: 0,
  // {Number} Maximum number of cached request (last in, first out queue system),
  // defaults to `false` for no limit. *Cannot be overridden per request*
  limit: false,
  // {Object} An instance of localforage, defaults to a custom in memory store.
  // *Cannot be overridden per request*
  store: new MemoryStore(),
  // {String|Function} Generate a unique cache key for the request.
  // Will use request url and serialized params by default.
  key: req => req.url + serializeQuery(req.params),
  // {Function} Invalidate stored cache. By default will remove cache when
  // making a request with method not `GET`, `POST`, `PUT`, `PATCH` or `DELETE` query.
  invalidate: async (cfg, req) => {
    const method = req.method.toLowerCase()
    if (method !== 'get') {
      await cfg.store.removeItem(cfg.uuid)
    }
  },
  // {Object} Define which kind of requests should be excluded from cache.
  exclude: {
    // {Array} List of regular expressions to match against request URLs.
    paths: [],
    // {Boolean} Exclude requests with query parameters.
    query: true,
    // {Function} Method which returns a `Boolean` to determine if request
    // should be excluded from cache.
    filter: null,
    // {Array} HTTP methods which will be excluded from cache.
    // Defaults to `['post', 'patch', 'put', 'delete']`
    // Any methods listed will also trigger cache invalidation while using the default `config.invalidate` method.
    //
    // Note: the HEAD method is always excluded (hard coded).
    // the OPTIONS method is ignored by this library as it is automatically handled by browsers/clients to resolve cross-site request permissions
    methods: ['post', 'patch', 'put', 'delete']
  },
  // {Boolean} Clear cached item when it is stale.
  clearOnStale: true,
  // {Boolean} Clear all cache when a cache write error occurs
  // (prevents size quota problems in `localStorage`).
  clearOnError: true,
  // {Function|Boolean} Determine if stale cache should be read when a network error occurs.
  readOnError: false,
  // {Boolean} Determine if response headers should be read to set `maxAge` automatically.
  // Will try to parse `cache-control` or `expires` headers.
  readHeaders: false,
  // {Boolean} Ignore cache, will force to interpret cache reads as a `cache-miss`.
  // Useful to bypass cache for a given request.
  ignoreCache: false,
  // {Function|Boolean} Print out debug log to console.
  debug: false
}

Returns

setupCache() returns an object containing the configured adapter, the cache store and the config that is applied to this instance.

setup(options)

Create an axios instance pre-configured with the cache adapter. Takes an options object to configure the cache and axios at the same time.

Options

{
  cache: {
    // Options passed to the `setupCache()` method
  }

  // Options passed to `axios.create()` method
}

All the other parameters will be passed directly to the axios.create method.

Returns

setup() returns an instance of axios pre-configured with the cache adapter. The cache store is conveniently attached to the axios instance as instance.cache for easy access.

RedisStore(client, [, hashKey])

RedisStore allow you to cache requests on server using redis. Create a RedisStore instance. Takes client (RedisClient) and optional hashKey (name of hashSet to be used in redis).

client

    // Using redis client https://github.com/NodeRedis/node_redis
    // We have tested it with node_redis v.2.8.0 but it's supposed to work smoothly with the comming releases.
    const redis = require("redis")
    const client = redis.createClient()

Returns

new RedisStore() returns an instance of RedisStore to be passed to setupCache() as store in config object.

Per request options

Using the same object definition as the setup method you can override cache options for individual requests.

api.get('https://httpbin.org/get', {
  cache: {
    // Options override
  }
})

All options except limit and store can be overridden per request.

Also the following keys are used internally and therefore should not be set in the options: adapter, uuid, acceptStale.

Building

npm run build

Webpack is used to build umd versions of the library that are placed in the dist folder.

  • cache.js
  • cache.min.js
  • cache.node.js
  • cache.node.min.js

A different version of axios-cache-adapter is generated for node and the browser due to how Webpack 4 uses a target to change how the UMD wrapper is generated using global or window. If you are using the library in node or in your front-end code while using a module bundler (Webpack, rollup, etc) the correct version will be picked up automatically thanks to the "main" and "browser" fields in the package.json.

axios-cache-adapter is developped in ES6+ and uses async/await syntax. It is transpiled to ES5 using babel with preset-env.

Testing

Tests are executed using karma.

To launch a single run tests using ChromeHeadless:

npm test

To launch tests in watch mode in Chrome for easier debugging with devtools:

npm run watch

Browser vs Node.js

axios-cache-adapter was designed to run in the browser. It does work in nodejs using the in memory store. But storing data in memory is not the greatests idea ever.

You can give a store to override the in memory store but it has to comply with the localForage API and localForage does not work in nodejs for very good reasons that are better explained in this issue.

The better choice if you want to use axios-cache-adapter server-side is to use a redis server with a RedisStore instance as explained above in the API section.

License

MIT © Carl Ogren