Storages
A storage adapter connects the cache interceptor to an in-memory or persistent data store. It saves and retrieves cache entries and serializes them when the underlying store requires it. You can use a built-in adapter, create your own, or install one published on npm.
The interceptors call the storage automatically. You can also reach the configured storage through axios.storage to inspect or invalidate entries manually.
The library includes these storages:
- Memory storage, created with
buildMemoryStorage(Node.js and web) - Web Storage API, created with
buildWebStorage(web only)
Concurrent requests
Concurrent request deduplication is local to each axios instance. While an eligible request is tracked in the instance’s in-memory waiting map, later requests for the same key wait for its result instead of reaching the network. Overrides and Vary mismatches can still produce additional requests.
A shared storage, such as Redis, shares completed cache entries but not the in-memory waiting map. If two processes request the same uncached key at the same time, both may send a network request. This is supported: each response completes normally, local waiting requests are settled, and they either read the shared result or retry if no result is available. The library does not provide a distributed lock or guarantee a single network request across processes.
Duplicate requests are normally harmless for the default GET and HEAD methods. If you enable caching for non-idempotent methods, you are responsible for making duplicate network requests safe.
Memory storage
TIP
This is the default storage.
Memory storage works in Node.js and browsers. Its entries are lost when the page reloads or the process exits.
By default, responses and cached entries share object references. Mutating a response can therefore mutate its cached value. Set cloneData to true to clone values returned by get(), or use 'double' to clone values on both set() and get(). See #163 and similar reports.
For long-running processes, use cleanupInterval, maxEntries, and maxStaleAge to bound memory usage and remove old entries.
The storage keeps entries in a JavaScript Map, exposed as storage.data.
import axios from 'axios';
import {
setupCache,
buildMemoryStorage
} from 'axios-cache-interceptor';
setupCache(axios, {
// Memory storage is already the default.
storage: buildMemoryStorage(
/* cloneData default=*/ false,
/* cleanupInterval default=*/ 5 * 60 * 1000,
/* maxEntries default=*/ 1024,
/* maxStaleAge default=*/ 60 * 60 * 1000
)
});Options:
cloneData: clones values returned byget()when set totrue. Use'double'to also clone values before saving them. The default isfalse.cleanupInterval: how often, in milliseconds, to remove old entries. Set it tofalseto disable automatic cleanup. The default is 5 minutes (300_000).maxEntries: the maximum number of entries to keep. Entry sizes cannot be measured reliably, so when the limit is reached the storage first removes entries that can be discarded (empty, must-revalidate, expired, or stale with a TTL beyondmaxStaleAge), then evicts by state (empty, must-revalidate, stale, cached and finally loading), oldest first. Set it tofalseto disable the limit. The default is1024.maxStaleAge: how long, in milliseconds, a stale entry with a defined TTL can remain after expiring before it is removed. The default is 1 hour (3_600_000).
Web Storage API
Use buildWebStorage to keep cached entries across page reloads. It stores entries as JSON in the browser’s Storage API.
import axios from 'axios';
import { setupCache, buildWebStorage } from 'axios-cache-interceptor';
setupCache(axios, {
// localStorage is shared with other code on the page, so
// prefix all keys to avoid collisions.
storage: buildWebStorage(localStorage, 'axios-cache:')
});import axios from 'axios';
import { setupCache, buildWebStorage } from 'axios-cache-interceptor';
setupCache(axios, {
// sessionStorage is shared with other code on the page, so
// prefix all keys to avoid collisions.
storage: buildWebStorage(sessionStorage, 'axios-cache:')
});import axios from 'axios';
import { setupCache, buildWebStorage } from 'axios-cache-interceptor';
// Any object implementing the Storage interface
const myStorage = createMyStorage();
setupCache(axios, {
storage: buildWebStorage(
myStorage,
'axios-cache:', // prefix
60 * 60 * 1000 // maxStaleAge (1 hour default)
)
});Options:
storage: theStorageinstance to use, such aslocalStorageorsessionStorage.prefix: a prefix added to every key to avoid collisions. The default is'axios-cache-'.maxStaleAge: how long, in milliseconds, a stale entry with a defined TTL can remain after expiring before it is removed. The default is 1 hour (3_600_000).
Browser quota
Since v0.9.0, the web storage evicts older entries when the browser’s storage quota is reached.
The storage handles quota errors as follows:
- Try to save the value.
- If the quota is exceeded, remove expired entries that cannot become stale.
- Retry the write. If it still fails, remove the oldest entry with the configured prefix.
- Repeat until the write succeeds or no matching entries remain.
- If the write still fails, leave the new value unstored. The value itself may exceed the available quota, or another application may be using the remaining capacity.
buildStorage()
All built-in storage adapters use buildStorage, and setupCache only accepts storages created by it. Use the same function to create a custom adapter from the following methods:
set(key: string, value: NotEmptyStorageValue, currentRequest?: CacheRequestConfig): MaybePromise<void>: saves a cache value under the given key.remove(key: string, currentRequest?: CacheRequestConfig): MaybePromise<void>: removes the value stored under the given key.find(key: string, currentRequest?: CacheRequestConfig): MaybePromise<StorageValue | undefined>: returns the stored value, orundefinedwhen the key does not exist.clear?(): MaybePromise<void>: optionally clears all stored data. The interceptor does not call this method; it is available for application-level invalidation.
find only needs to return what was saved. The storage returned by buildStorage checks expiration itself, turning expired entries into stale ones or removing them.
Third-party storages
The following examples are not maintained as separate packages. Use them as starting points and adapt their expiration and serialization behavior to your application.
- Node Redis v4
- IndexedDB
- Node Cache
- Open a pull request to add another example.
Node Redis storage
This example uses the Node Redis v4 client. The PXAT option gives every entry an absolute expiration time, including temporary loading entries left behind by interrupted requests.
The expiration of a loading entry is only cleanup for abandoned requests. It does not provide distributed request deduplication; multiple processes can still send the same uncached request concurrently.
import { createClient } from 'redis';
import {
buildStorage,
canStale,
type CacheRequestConfig,
type NotEmptyStorageValue,
type StorageValue
} from 'axios-cache-interceptor';
const client = createClient(/* connection config */);
await client.connect();
function getExpiresAt(
value: NotEmptyStorageValue,
request?: CacheRequestConfig
) {
switch (value.state) {
case 'loading': {
// Abandoned loading entries must not remain forever.
const requestTtl =
request?.cache && typeof request.cache.ttl === 'number'
? request.cache.ttl
: 60_000;
return Date.now() + requestTtl;
}
case 'stale':
if (value.ttl) {
return value.createdAt + value.ttl;
}
break;
case 'cached': {
// Keep entries that can become stale long enough to be revalidated.
const staleFor = Math.max(
value.staleTtl ?? 0,
canStale(value) ? 60 * 60 * 1000 : 0
);
return value.createdAt + value.ttl + staleFor;
}
}
// Keep stale entries without a TTL for at most one hour.
return Date.now() + 60 * 60 * 1000;
}
const redisStorage = buildStorage({
async find(key) {
const result = await client.get(`axios-cache-${key}`);
return result ? (JSON.parse(result) as StorageValue) : undefined;
},
async set(key, value, request) {
await client.set(`axios-cache-${key}`, JSON.stringify(value), {
PXAT: getExpiresAt(value, request)
});
},
async remove(key) {
await client.del(`axios-cache-${key}`);
}
});You can use buildStorage to integrate other systems such as localForage, ioredis, or Memcached.
IndexedDB
This example uses idb-keyval to store cache entries in IndexedDB.
import { buildStorage } from 'axios-cache-interceptor';
import { del, get, set } from 'idb-keyval';
const indexedDbStorage = buildStorage({
async find(key) {
const value = await get(key);
if (!value) {
return;
}
return JSON.parse(value);
},
async set(key, value) {
await set(key, JSON.stringify(value));
},
async remove(key) {
await del(key);
}
});Node Cache
This example uses node-cache. Check the package’s current maintenance status before using it in a new application.
import { buildStorage } from 'axios-cache-interceptor';
import NodeCache from 'node-cache';
const cache = new NodeCache({ stdTTL: 60 * 60 * 24 * 7 });
const cacheStorage = buildStorage({
find(key) {
return cache.get(key);
},
set(key, value) {
cache.set(key, value);
},
remove(key) {
cache.del(key);
}
});