--- url: 'https://axios-cache-interceptor.js.org/guide/debugging.md' --- # Debugging At some point, you may encounter cache behavior that does not match expectations. To help diagnose such issues, the library provides a separate build with debug logging enabled. You can use it by changing the `setupCache` import: ::: code-group ```ts [EcmaScript] import Axios from 'axios'; // Only import from `/dev` where you import `setupCache`. import { setupCache } from 'axios-cache-interceptor'; // [!code --] import { setupCache } from 'axios-cache-interceptor/dev'; // [!code ++] // same object, but with updated typings. const axios = setupCache(Axios, { debug: console.log // [!code ++] }); ``` ```ts [Common JS] const Axios = require('axios'); // Only import from `/dev` where you import `setupCache`. const { setupCache } = require('axios-cache-interceptor'); // [!code --] const { setupCache } = require('axios-cache-interceptor/dev'); // [!code ++] // same object, but with updated typings. const axios = setupCache(Axios, { debug: console.log // [!code ++] }); ``` ```ts{3,4} [Browser] const Axios = window.axios; // Choose development bundle. // [!code ++] const { setupCache } = window.AxiosCacheInterceptor; // same object, but with updated typings. const axios = setupCache(Axios, { debug: console.log // [!code ++] }); ``` ```ts {5,11} [Skypack] import Axios from 'https://cdn.skypack.dev/axios'; // Only import from `/dev` where you import `setupCache`. import { setupCache } from 'https://cdn.skypack.dev/axios-cache-interceptor'; // [!code --] import { setupCache } from 'https://cdn.skypack.dev/axios-cache-interceptor/dev'; // [!code ++] // same object, but with updated typings. const axios = setupCache(Axios, { debug: console.log // [!code ++] }); ``` ::: And much more, depending on your context, situation, and configuration. **Any misbehavior you encounter will have a log to explain it.** ::: details Sample of logs sent to console. ```json [ { "id": "-644704205", "msg": "Sending request, waiting …", "data": { "overrideCache": false, "state": "empty" } }, { "id": "-644704205", "msg": "Waiting list had a deferred for this key, waiting for it to finish" }, { "id": "-644704205", "msg": "Detected concurrent request, waiting for it to finish" }, { "id": "-644704205", "msg": "Useful response configuration found", "data": { "cacheConfig": { /*...*/ }, "cacheResponse": { "data": { /*...*/ }, "status": 200, "statusText": "OK", "headers": { /*...*/ } } } }, { "id": "-644704205", "msg": "Found waiting deferred(s) and resolved them" }, { "id": "-644704205", "msg": "Returning cached response" }, // First request ended, second call below: { "id": "-644704205", "msg": "Response cached", "data": { "cache": { /*...*/ }, "response": { /*...*/ } } }, { "id": "-644704205", "msg": "Returning cached response" } ] ``` ::: --- --- url: 'https://axios-cache-interceptor.js.org/guide/getting-started.md' --- # Getting Started [Looking for axios v0?](https://axios-cache-interceptor.js.org/v0/) ## Install Add Axios Cache Interceptor and Axios to your project using your favorite package manager: ::: code-group ```bash [NPM] npm install axios@^1 axios-cache-interceptor@^1 ``` ```html [Browser] ``` ```ts [Skypack] import Axios from 'https://cdn.skypack.dev/axios'; import { setupCache } from 'https://cdn.skypack.dev/axios-cache-interceptor'; ``` ::: ## Setup After installing, you can import the package and apply the interceptor to your axios instance, as shown below: ::: code-group ```ts [EcmaScript] import Axios from 'axios'; import { setupCache } from 'axios-cache-interceptor'; const instance = Axios.create(); // [!code focus] const axios = setupCache(instance); // [!code focus] const req1 = axios.get('https://api.example.com/'); // [!code focus] const req2 = axios.get('https://api.example.com/'); // [!code focus] const [res1, res2] = await Promise.all([req1, req2]); res1.cached; // false // [!code focus] res2.cached; // true // [!code focus] ``` ```ts [CommonJS] const Axios = require('axios'); const { setupCache } = require('axios-cache-interceptor'); const instance = Axios.create(); // [!code focus] const axios = setupCache(instance); // [!code focus] const req1 = axios.get('https://api.example.com/'); // [!code focus] const req2 = axios.get('https://api.example.com/'); // [!code focus] const [res1, res2] = await Promise.all([req1, req2]); res1.cached; // false // [!code focus] res2.cached; // true // [!code focus] ``` ```ts [Browser] const Axios = window.axios; const { setupCache } = window.AxiosCacheInterceptor; const instance = Axios.create(); // [!code focus] const axios = setupCache(instance); // [!code focus] const req1 = axios.get('https://api.example.com/'); // [!code focus] const req2 = axios.get('https://api.example.com/'); // [!code focus] const [res1, res2] = await Promise.all([req1, req2]); res1.cached; // false // [!code focus] res2.cached; // true // [!code focus] ``` ```ts [Skypack] import Axios from 'https://cdn.skypack.dev/axios'; import { setupCache } from 'https://cdn.skypack.dev/axios-cache-interceptor'; const instance = Axios.create(); // [!code focus] const axios = setupCache(instance); // [!code focus] const req1 = axios.get('https://api.example.com/'); // [!code focus] const req2 = axios.get('https://api.example.com/'); // [!code focus] const [res1, res2] = await Promise.all([req1, req2]); res1.cached; // false // [!code focus] res2.cached; // true // [!code focus] ``` ::: `setupCache(instance)` attaches both cache interceptors by default. If you need custom interceptor ordering, see [Other Interceptors](./interceptors.md). Just the above is sufficient for most use cases. However, you can also customize each cache behavior by passing a configuration object to the `setupCache` function. And you can also customize some behaviors per request by using the `cache` option in the request config. ## Support Table Most of axios v0 breaking changes were about typing issues, so your version may work with one outside of this table. **Axios and Axios Cache Interceptor v0 are not compatible with Axios and Axios Cache Interceptor v1** > **Note**: Axios was not defined as a `peerDependency` for all v0 versions, because it > had a non-stable semver version. > [See #145 (Comment)](https://github.com/arthurfiorette/axios-cache-interceptor/issues/145#issuecomment-1042710481) | [Axios](https://github.com/axios/axios/releases) | [Axios Cache Interceptor](https://github.com/arthurfiorette/axios-cache-interceptor/releases) | | ------------------------------------------------ | --------------------------------------------------------------------------------------------- | | `>= v1.7.8` | `>= v1.7.0` | | `>= v1.6` | `>= v1.3.0 && <= 1.6.2` | | `>= v1.4` | `>= v1.2.0` | | `>= v1.3.1` | `>= v1` | | `>= v0.27` | `>= v0.10.3` | | `>= v0.26` | `>= v0.8.4` | | `~ v0.25` | `~ v0.8.4` | | `~ v0.24` | `>= v0.5 && <= 0.8.3` | | `~ v0.23` | `~ v0.4` | | `~ v0.22` | `~ v0.3` | | `v0.21` | `<= v0.2` | ## AI / LLM Skills If you are using an AI coding assistant, you can install the axios-cache-interceptor skill to give it up-to-date context about this library: ```bash npx skills add arthurfiorette/axios-cache-interceptor ``` This uses the [Skills](https://github.com/vercel-labs/skills) framework. Once installed, your AI assistant will have access to the full API reference and usage patterns for this library. ### Read More Some useful links to get you more familiar with the library: * [Debugging requests](./debugging.md) * [Storages](./storages.md) * [Global config](../config.md) * [Per request config](../config/request-specifics.md) * [Response object](../config/response-object.md) --- --- url: 'https://axios-cache-interceptor.js.org/guide.md' --- # Introduction Axios Cache Interceptor is an interceptor for axios, as its name says, to handle caching. It was created to help developers call axios multiple times without having to worry about overloading the network or implementing a simple and error-prone cache system. Each request goes through an interceptor applied to your axios instance. There, we handle each request and decide if we should send it to the network or return a cached response. ## How it works By using axios interceptors instead of adapters, each request is passed through the interceptor before calling the adapter and before returning to the original caller. Adapters are the final step and they are responsible for the actual network call, so, by choosing to use interceptors, we create a minimally invasive approach that allows you to still use the axios adapter of your choice. Before the request is delivered to the adapter, our request interceptor checks if the request has already been cached and if it's valid, checks if the request should be cached (sometimes you don't want cache at all, and that's ok), if there's already a request sent to the network that we can wait for, and many other checks. After the adapter gets the response, we check if it belongs to a *cacheable* request, saves it to the storage, resolves other requests waiting for the same resource and finally returns the response to the original caller. ## Features * TTL, Cache-Control and ETag. * Return previous cached request if the new one failed. * Handles parallel requests * 100% Customizable * Built-in storages like In-Memory, Local Storage and Session Storage. * Less than 4.3Kb minified and gzipped. * Development mode to debug your requests. * 22 times faster than using axios and 8% faster than `axios-cache-adapter`. * And much more... ## Why not...? ### axios-cache-adapter The creation of this library is heavily inspired by axios-cache-adapter. It was a great library but now it is unmaintained and has a lot of unresolved issues. Also, it weighs more than 4x the size of this library with fewer features and less performance. ### Fetch and some state management library? As this library was built to be used with axios and to handle storage itself, I can assure that it is more performant than any hand-rolled code you may find or write yourself. About state management libraries and other similar things, [this blog post](https://arthur.place/implications-of-cache-or-state) explains why cache is the more correct, architectural way, instead of state. --- --- url: 'https://axios-cache-interceptor.js.org/guide/invalidating-cache.md' --- # Invalidating Cache When using cache-first approaches to improve performance, data inconsistency becomes your major problem. That occurs because **you** can mutate data on the server and so can **others**. It becomes impossible to really know what the current state of the data is in real time without communicating with the server. ::: warning **All available revalidation methods only work when the request is successful.** If you are wanting to revalidate with a non standard `2XX` status code, make sure to enable it at [`validateStatus`](https://axios-http.com/docs/handling_errors) or revalidate it manually as shown [below](#updating-cache-through-external-sources). ::: Take a look at this simple example: 1. User lists all available posts, the server returns an empty array. 2. User proceeds to create a new post, server returns 200 OK. 3. Your frontend navigates to the post list page. 4. The post list page still shows 0 posts because it had a recent cache for that request. 5. Your client shows 0 posts, but the server actually has 1 post. ## Revalidation after mutation In most cases, you are responsible for that inconsistency — as in the example above, where the client itself initiated the mutation request. When that happens, you can invalidate the cache for all the entries you have changed. **The `cache.update` option is available for every request that you make, and it will be the go-to tool for invalidation.** ::: tip By centralizing your requests into separate methods, you are more likely to keep track of custom IDs you use for each request, thus making it easier to reference and invalidate after. ::: ## Programmatically If the mutation you made was just simple changes, you can get the mutation response and programmatically update your cache. Again considering the first example, we can just do an `array.push` to the `list-posts` cache and we are good to go. ```ts // Uses `list-posts` id to be able to reference it later. function listPosts() { return axios.get('/posts', { id: 'list-posts' }); } function createPost(data) { return axios.post( '/posts', data, /* [!code focus:25] */ { cache: { update: { // Will perform a cache update for the `list-posts` respective // cache entry. 'list-posts': (listPostsCache, createPostResponse) => { // If the cache doesn't have a cached state, we don't need // to update it if (listPostsCache.state !== 'cached') { return 'ignore'; } // Imagine the server response for the `list-posts` request // is: { posts: Post[]; }, and the `create-post` response // comes with the newly created post. // Adds the created post to the end of the post's list listPostsCache.data.posts.push(createPostResponse.data); // Return the same cache state, but a updated one. return listPostsCache; } } } } ); } ``` This will update the `list-posts` cache at the client side, making it equal to the server. When such operations are possible, they are the preferred approach. That's because we do not contact the server again and can update the cache ourselves. ::: tip **Note to Vue users:** If you modify an array as shown above and then assign the result data of the axios request to a Vue `ref`, you may have issues with the UI not updating. This is because the cached array is the same object as was returned from the previous request. You need to copy the array before modifying it: ```ts listPostsCache.data.posts = [...listPostsCache.data.posts]; listPostsCache.data.posts.push(createPostResponse.data); // or listPostsCache.data.posts = [...listPostsCache.data.posts, createPostResponse.data]; ``` or before assigning it to the `ref`: ```ts myRef.value = [...axios.get(url).data]; ``` ::: ## Through network Sometimes, the mutation you made is not simple enough and would need a lot of copied service code to replicate all changes the backend made, turning it into a duplication and maintenance nightmare. In those cases, you can just invalidate the cache and let the next request be forwarded to the server, and update the cache with the new network response. ```ts // Uses `list-posts` id to be able to reference it later. function listPosts() { return axios.get('/posts', { // [!code focus:3] id: 'list-posts' }); } function createPost(data) { return axios.post('/posts', data, { // [!code focus:9] cache: { update: { // Internally calls the storage.remove('list-posts') and lets the // next request be forwarded to the server without you having to // do any checks. 'list-posts': 'delete' } } }); } ``` Continuing with the first example, at step **3**, the axios-cache-interceptor instance will automatically request the server again and apply the necessary cache changes before the promise resolves and your page gets rendered. ## Through external sources If you have any other type of external communication, such as a WebSocket listener for changes, you may want to update your axios cache outside of a request context. For that, you can operate the storage manually. It is simple as that: ```ts if (someLogicThatShowsIfTheCacheShouldBeInvalidated) { // Deletes the current cache for the `list-posts` respective request. await axios.storage.remove('list-posts'); } if (someLogicThatShowsIfTheCacheShouldBeInvalidated) { // Deletes all cached data await axios.storage.clear(); } ``` ## Keeping cache up to date If you were **not** the one responsible for that change, your client may not be aware that it has changed. E.g. When you are using a chat application, you may not be aware that a new message was sent to you. In such cases that we **do not** have a way to know that the cache is outdated, you may have to end up setting a custom time to live (TTL) for specific requests. ```ts // Uses `list-posts` id to be able to reference it later. function listPosts() { return axios.get('/posts', { id: 'list-posts', cache: { ttl: 1000 * 60 // 1 minute. } }); } function createPost(data) { return axios.post('/posts', data, { cache: { update: { // I still want to delete the cache when I KNOW things have // changed, but, by setting a TTL of 1 minute, I ensure that // 1 minute is the highest time interval that the cache MAY // get outdated. 'list-posts': 'delete' } } }); } ``` ## Summary When applying any kind of cache to any application, you choose to trade data consistency for performance. And, most of the time, that is OK. *The best cache strategy is a combination of all of them. TTL, custom revalidation, stale while revalidate and all the others together are the best solution.* The only real advice is to weigh the amount of inconsistency you are willing to accept against the performance you want to gain. **Sometimes, not caching is the best solution.** --- --- url: 'https://axios-cache-interceptor.js.org/guide/interceptors.md' --- # Other Interceptors When combining `axios-cache-interceptor` with other interceptors, execution order matters. This page explains the default behavior and how to customize it. ## TL;DR * **Request** interceptors registered **before** `setupCache()` run **after** the cache interceptor; those registered **after** `setupCache()` run **before** the cache interceptor. * **Response** interceptors registered **before** `setupCache()` run **before** the cache interceptor; those registered **after** `setupCache()` run **after** the cache interceptor. * By default, `setupCache(axios)` attaches both cache interceptors immediately. * You can disable automatic registration with `register: false` and register manually. ## Explanation Axios interceptors are run differently for the request and response ones. * **Request interceptors** are executed in **reverse order** - the last interceptor added runs first (LIFO - *Last In First Out*) * **Response interceptors** are executed in **normal order** - the first interceptor added runs first (FIFO - *First In First Out*) As explained better in the [Axios documentation](https://github.com/axios/axios#interceptors) and in [this issue](https://github.com/arthurfiorette/axios-cache-interceptor/issues/449#issuecomment-1370327566). ```ts // This will run AFTER the cache interceptor axios.interceptors.request.use((req) => req); // This will run BEFORE the cache interceptor axios.interceptors.response.use((res) => res); setupCache(axios); // This will run BEFORE the cache interceptor axios.interceptors.request.use((req) => req); // This will run AFTER the cache interceptor axios.interceptors.response.use((res) => res); ``` ## Custom order If you need full control, disable automatic registration and register cache interceptors yourself. ```ts import Axios from 'axios'; import { setupCache } from 'axios-cache-interceptor'; const axios = setupCache(Axios.create(), { register: false }); // Register cache response interceptor first (response interceptors are FIFO) axios.interceptors.response.use( axios.responseInterceptor.onFulfilled, axios.responseInterceptor.onRejected ); // Register your own interceptors axios.interceptors.request.use((req) => req); axios.interceptors.response.use((res) => res); // Register cache request interceptor last (request interceptors are LIFO) axios.interceptors.request.use( axios.requestInterceptor.onFulfilled, axios.requestInterceptor.onRejected ); ``` *** ## Extending types When using axios-cache-interceptor, you'll notice that it has a different type than the defaults `AxiosInstance`, `AxiosRequestConfig` and `AxiosResponse`. That's because we chose to override axios's interfaces instead of extending, to avoid breaking changes with other libraries. However, this also means that when integrating with other packages or creating your own custom interceptor, you need to override/extend our own types, `CacheInstance`, `CacheRequestConfig` and `CacheAxiosResponse` to match your needs. This can be done as shown below: ```ts declare module 'axios-cache-interceptor' { interface CacheRequestConfig { customProperty: string; } } ``` ## Streams and non-JSON Sometimes you may want to cache a response that is not `JSON`, or that is a `Stream`. Either created by another interceptor or even by the axios adapter itself. To do so, you can use axios's native `transformResponse` option, which is a function that receives the response and returns a string or a buffer. **Axios Cache Interceptor** can only handle serializable data types, so you need to convert the response to a string or a buffer. ```ts import Axios from 'axios'; import { setupCache } from 'axios-cache-interceptor'; const instance = Axios.create(); const axios = setupCache(instance); // [!code focus:8] const response = await axios.get('my-url-that-returns-a-stream', { responseType: 'stream', transformResponse(response) { // You will need to implement this function. return convertStreamToStringOrObject(response.data); } }); response.data; // Will be a string and will be able to be cached. ``` This library cannot handle streams or buffers, so if you still need `response.data` to be a stream or buffer, you will need to cache it manually. If you can collect the response data into a serializable format, `axios-cache-interceptor` can handle it for you with help of the `transformResponse` option. ## Custom Adapters If you are writing a custom Axios adapter, **always throw an `AxiosError`** (not a plain `Error` or any other type) when the request fails. The cache interceptor relies on the `AxiosError.config` property to identify which in-flight request failed so it can clean up internal state (the deferred waiting map and the loading cache entry). If your adapter throws a non-`AxiosError`, the interceptor cannot determine which request failed, which will leave the cache entry stuck in a `loading` state and cause all subsequent requests to that key to hang indefinitely. The development build will log a debug message when this happens. ```ts import { AxiosError } from 'axios'; // ✅ Correct – always throw AxiosError const myAdapter = async (config) => { try { // ... perform request ... } catch (err) { throw new AxiosError(err.message, err.code, config); } }; // ❌ Incorrect – plain errors bypass the cache error handler const badAdapter = async (config) => { throw new TypeError('socket hang up'); // cache state becomes stuck! }; ``` --- --- url: 'https://axios-cache-interceptor.js.org/guide/request-id.md' --- # Request Id We can distinguish requests from each other by assigning a unique `id` to each request. These IDs are provided to the storage as cache keys. Each ID is responsible for binding a cache to its request, for referencing or invalidating it later and to make the interceptor use the same cache for requests to the same endpoint and parameters. The default id generator is smart enough to generate the same ID for theoretically same requests. `{ baseURL: 'https://a.com/', url: '/b' }` **==** `{ url: 'https://a.com/b/' }`. ::: code-group ```ts [Different requests] import Axios from 'axios'; import { setupCache } from 'axios-cache-interceptor'; const axios = setupCache(Axios); // [!code focus:5] // These two requests are from completely different endpoints, but they will share // the same resources and cache, as both have the same ID. const reqA = await axios.get('/a', { id: 'custom-id' }); const reqB = await axios.get('/b', { id: 'custom-id' }); ``` ```ts [Different contexts] import Axios from 'axios'; import { setupCache } from 'axios-cache-interceptor'; const axios = setupCache(Axios); // [!code focus:7] // You can use the same logic to create two caches for the same endpoint. // Allows you to have different use cases for the coincident same endpoint. const userForPageX = await axios.get('/users', { id: 'users-page-x' }); const userForPageY = await axios.get('/users', { id: 'users-page-y' }); ``` ::: ::: warning If you forcefully send two different requests with the same ID, this library will ignore any possible differences between them and share the same cache for both. ::: ## Custom Generator By default, the id generator extracts `method`, `baseURL`, `query`, `params`, `data` and `url` properties from the request object and hashes it into a number with [`object-code`](https://www.npmjs.com/package/object-code). While this default implementation offers reasonable uniqueness for most scenarios, it's worth noting that there's a [theoretical 50% probability of collisions after approximately 77,000 keys](https://preshing.com/20110504/hash-collision-probabilities/) have been generated. However, this limitation is typically inconsequential in browser environments due to their 5MB storage limit, which is reached long before the collision threshold. ::: warning Consider implementing a custom key generator function using libraries like [`object-hash`](https://www.npmjs.com/package/object-hash) for generating hash keys with significantly lower collision probabilities when hitting over 77K unique keys is a possibility ::: Here's an example of a generator that only uses the `url`, `method`, and `custom` properties: ```ts import Axios from 'axios'; import { setupCache, buildKeyGenerator } from 'axios-cache-interceptor'; const axios = setupCache(Axios, { generateKey: buildKeyGenerator((request /* [!code focus:5] */) => ({ method: request.method, url: request.url, custom: logicWith(request.method, request.url) })) }); ``` --- --- url: 'https://axios-cache-interceptor.js.org/config/request-specifics.md' --- # Request specifics Each request can have its own cache customization, by using the `cache` property. This way, you can have requests behaving differently from each other without much effort. The inline documentation is self explanatory, but here is a brief overview of what each property does: ::: tip You can override every request specific property when creating the cached axios client, the same way you do with the [global options](../config.md). ::: ## id * Type: `string` * default: *(auto generated by the current [key generator](../guide/request-id.md#custom-generator))* The [Request ID](../guide/request-id.md) used in this request. It may have been generated by the [Key Generator](../guide/request-id.md#custom-generator) or a custom one provided by [`config.id`](./request-specifics.md#id) ## cache * Type: `Partial>`. * Default: `{}` *(Inherits from global configuration)* ::: tip As this property is optional, when not provided, all properties will inherit from global configuration ::: The cache option available through the request config is where all the cache customization happens. You can pass an object with cache properties to customize cache behavior. To disable caching for a specific request, use `cache: { enabled: false }`: ```ts // Make a request with cache disabled const { id: requestId } = await axios.get('url', { cache: { enabled: false } }); // Delete the cache entry for this request if needed await axios.storage.remove(requestId); ``` ## cache.enabled * Type: `boolean` * Default: `true` Whether the cache is enabled for this request. When set to `false`, the cache will be completely disabled for this request. This is useful for **opt-in cache** scenarios where you want to disable cache globally but enable it for specific requests. ### Example: Opt-in Cache Pattern You can disable cache by default and enable it only for specific endpoints: ```ts import { setupCache } from 'axios-cache-interceptor'; // Setup axios with cache disabled by default const axios = setupCache(axiosInstance, { enabled: false // Disable cache globally }); // Most requests won't use cache await axios.get('/api/realtime-data'); // Not cached // Enable cache for specific heavy/expensive requests await axios.get('/api/heavy-computation', { cache: { enabled: true, ttl: 1000 * 60 * 10 // Cache for 10 minutes } }); // Cached ``` ### Example: Traditional Pattern (Opt-out) The traditional pattern where cache is enabled by default: ```ts import { setupCache } from 'axios-cache-interceptor'; // Setup axios with cache enabled by default (this is the default behavior) const axios = setupCache(axiosInstance, { enabled: true // or omit this as true is the default }); // Most requests will use cache await axios.get('/api/user-profile'); // Cached // Disable cache for specific real-time endpoints await axios.get('/api/live-stock-prices', { cache: { enabled: false } }); // Not cached ``` ## cache.ttl * Type: `number | ((response: CacheAxiosResponse) => number | Promise)` * Default: `1000 * 60 * 5` *(5 Minutes)* ::: warning When using [**interpretHeader**](#cache-interpretheader), this value will only be used if the interpreter can't determine their TTL value to override this one. ::: The time until the cached value is expired in milliseconds. If a function is used, it will receive the complete response and should return a TTL value The `ttl` is only applied when the response is **first cached**; changing it on follow-up requests has no effect. See [#1024](https://github.com/arthurfiorette/axios-cache-interceptor/issues/1024) for workarounds. ## cache.interpretHeader * Type: `boolean` * Default: `true` If enabled, when the response is received, the `ttl` property will be inferred from the response headers, as described in the MDN docs and the HTTP specification. See the actual implementation of the [`interpretHeader`](https://github.com/arthurfiorette/axios-cache-interceptor/blob/main/src/header/interpreter.ts) method for more information. ## cache.cacheTakeover * Type: `boolean` * Default: `true` As most of our cache strategies depend on well-known HTTP headers, most browsers also use those headers to define their own cache strategies and storages. ::: details This can be seen when opening network tab in your browser's dev tools. ![Network tab in Chrome Dev Tools](../assets/disk-cache-screenshot.png "A network disk-cache example") ::: When your requested routes include `Cache-Control` in their responses, you may end up with both the library and your browser caching the response, resulting in a **double layer of cache**. This option solves this by including predefined headers in the request that instruct any client/adapter to not cache the response, thus ensuring only the library caches it. **These headers are added to your specific request and won't affect any other request or response that the server may handle.** Headers included: * `Cache-Control: no-cache, no-store, must-revalidate, max-age=0` * `Pragma: no-cache` * `Expires: 0` ::: info Safari Compatibility The `max-age=0` directive was added to ensure compatibility with Safari (including iOS Safari), which has historically been more aggressive with caching and may not fully respect the `no-cache` directive alone. This combination of headers ensures reliable cache prevention across all major browsers. ::: ::: tip Alternative While `cacheTakeover` works for most browsers according to [this StackOverflow answer](https://stackoverflow.com/a/2068407), in some rare edge cases it may be unreliable due to browser-specific cache behaviors or network intermediaries. For maximum reliability, add a unique random query parameter instead: ```ts axios.get( `/api/data?cachebuster=${Math.random().toString(36).slice(2)}`, { id: 'api-data-endpoint' // Keep same cache key despite different URLs } ); ``` Your backend can ignore the `cachebuster` value. This **guarantees** no browser caching while preserving axios-cache-interceptor functionality. ::: ::: warning CORS Considerations This option will not work on **CORS** requests with restricted headers, as the browser will throw: `Request header field Pragma is not allowed by Access-Control-Allow-Headers in preflight response.` When you encounter CORS errors, you need to ensure `Cache-Control`, `Pragma`, and `Expires` headers are included in your server's `Access-Control-Allow-Headers` CORS configuration. If you cannot modify the CORS configuration, you can: 1. Disable this option (`cacheTakeover: false`) 2. Use the query parameter approach mentioned above Learn more about why this should be enabled at [#437](https://github.com/arthurfiorette/axios-cache-interceptor/issues/437#issuecomment-1361262194) and in this [StackOverflow answer](https://stackoverflow.com/a/2068407). ::: ## cache.methods * Type: `Method[]` * Default: `["get", "head"]` Specifies which methods we should handle and cache. This is where you can enable caching to `POST`, `PUT`, `DELETE` and other methods, as the default is only `GET`. If you want to enable cache for `POST` requests, you can do: ```ts // Globally enables caching for POST requests const axios = setupCache(instance, { methods: ['get', 'post'] }); // Just for this request axios.post('url', data, { cache: { methods: ['post'] } }); ``` We use `methods` in a per-request configuration setup because sometimes you have exceptions to the method rule. ## cache.cachePredicate * Type: `CachePredicate` * Default: `{ statusCheck: (status) => [200, 203, 300, 301, 302, 404, 405, 410, 414, 501].includes(status) }` *(These default status codes follows RFC 7231)* An object or function that will be tested against the response to indicate if it can be cached. You can use `statusCheck`, `containsHeader`, `ignoreUrls`, `allowUrls` and `responseMatch` to test against the response. If both `ignoreUrls` & `allowUrls` are matched, `ignoreUrls` take precedence. ```ts{5,8,13} axios.get<{ auth: { status: string } }>('url', { cache: { cachePredicate: { // Only cache if the response comes with a "good" status code statusCheck: (status) => true, // some calculation // Tests against any header present in the response. containsHeaders: { 'x-custom-header-3': (value) => true // some calculation }, // Check custom response body responseMatch: ({ data }) => { // Sample that only caches if the response is authenticated return data.auth.status === 'authenticated'; }, // Ensures no request is cached if its url starts with "/api" ignoreUrls: [/^\/api/] // only cache request urls that includes "weekly" allowUrls: ['weekly'] } } }); ``` ## cache.update * Type: `CacheUpdater` * Default: `{}` Once the request is resolved, this specifies what other responses should change their cache. Can be used to update the request or delete other caches. It is a simple `Record` with the request id. Here's an example with some basic logic: Using a function instead of an object is supported but not recommended, as it's better to just consume the response normally and write your own code after it. But it\`s here in case you need it. ```ts // Some requests id's let profileInfoId; let userInfoId; axios.post<{ auth: { user: User } }>( 'login', { username, password }, { cache: { update: { // Evicts the profile info cache, because the user is now authenticated and the response needs to be re-fetched [profileInfoId]: 'delete', // An example that update the "user info response cache" when doing a login. // Imagine this request is a login one. [userInfoResponseId]: (cachedValue, response) => { if (cachedValue.state !== 'cached') { // Only needs to update if the response is cached return 'ignore'; } cachedValue.data = data; // This returned value will be returned in next calls to the cache. return cachedValue; } } } } ); ``` ## cache.etag * Type: `string | boolean` * Default: `true` Configures [`ETag`](https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Headers/ETag) and [`If-None-Match`](https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Headers/If-None-Match) header handling for cache revalidation. To use `true` (automatic ETag handling), `interpretHeader` option must be set to `true`. ## cache.modifiedSince * Type: `boolean` * Default: `true` Use [`If-Modified-Since`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since) header in this request. Use a date to force a custom static value or true to use the last cached timestamp. If never cached before, the header is not set. If `interpretHeader` is set and a [`Last-Modified`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified) header is sent to us, then value from that header is used, otherwise cache creation timestamp will be sent in [`If-Modified-Since`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since). ## cache.staleIfError * Type: `number | boolean | StaleIfErrorPredicate` * Default: `true` Enables cache to be returned if the response comes with an error, either by invalid status code, network errors and etc. You can filter the type of error that should be stale by using a predicate function. ::: warning If the response is treated as error because of invalid status code *(like when using [statusCheck](#cache-cachepredicate))*, and this ends up `true`, the cache will be preserved over the "invalid" request. So, if you want to preserve the response, you can use the below predicate: ::: ```ts const customPredicate = (response, cache, error) => { // Blocks staleIfError if has a response return !response; // Note that this still respects axios default implementation // and throws an error (but it keeps the response) }; axios.get('/api/data', { cache: { staleIfError: customPredicate } }); ``` ## cache.override * Type: `boolean` * Default: `false` This option bypasses the current cache and always make a new http request. This will not delete the current cache, it will just replace the cache when the response arrives. Unlike as `cache: false`, this will not disable the cache, it will just ignore the pre-request cache checks before making the request. This way, all post-request options are still available and will work as expected. ## cache.vary * Type: `string[] | boolean` * Default: `true` Configure HTTP Vary header handling. * `true`: Automatic vary handling (default, recommended) * `false`: Disable vary checking (**WARNING: can cause cache poisoning**) * `string[]`: Overrides server sent Vary and use specific request headers in cache key When the server responds with a `Vary` header, the cache key is adjusted to include the specified request headers: ```ts // Request with Authorization header await axios.get('/api/users', { headers: { authorization: 'Bearer token-A' } }); // Server responds: Vary: Authorization // Cached with ID based on: url + method + params + {authorization: 'Bearer token-A'} // Different authorization = different cache await axios.get('/api/users', { headers: { authorization: 'Bearer token-B' } }); // Gets its own cache (different ID due to different authorization) ``` ## cache.hydrate * Type: `undefined | ((cache: StorageValue) => void | Promise)` * Default: `undefined` Asynchronously called when a network request is needed to resolve the data, but an older one **and probably expired** cache exists. Calls it with the current data **BEFORE** the network request starts, so you can use it to temporarily update your UI with expired data before the network returns. Hydrating your components with old data before the network resolves with the newer one is better than *flickering* your entire UI. This is even better when dealing with slower networks and persisted cache, like for mobile apps. ::: warning If the axios call will return cached data, meaning no network will be involved, the hydrate **IS NOT CALLED**, as the axios promise will be resolved instantly. ::: ```ts {7,13} // Example of function that receives data and renders into a screen function render() {} const response = await axios.get(/* [!code focus:10] */ 'url', { // This is called instantly if axios needs to make a network request cache: { hydrate: (cache) => render(cache.data) } }); // After the network lookup ends, we have fresh data and can // re-render the UI with confidence render(response.data); ``` --- --- url: 'https://axios-cache-interceptor.js.org/config/response-object.md' --- # Response object Axios Cache Interceptor returns a slightly different response than the standard axios response. It contains information about the cache and other needed properties. ## id * Type: `string` The [Request ID](../guide/request-id.md) used in this request. It may have been generated by the [Key Generator](../guide/request-id.md#custom-generator) or a custom one provided by [`config.id`](./request-specifics.md#id) ```ts const response = await axios.get('url', { id: 'my-overridden-id' }); // This request used 'my-overridden-id' and // not the one generated by the key generator response.id === 'my-overridden-id'; ``` ## cached * Type: `boolean` A simple boolean indicating if the request returned data from the cache or from the network call. ::: tip This does not indicate if the request was capable of being cached or not, as options like [`cache.override`](./request-specifics.md#cache-override) may have been enabled. ::: ## stale * Type: `boolean` A simple boolean indicating whether the returned data is from a valid or stale cache. --- --- url: 'https://axios-cache-interceptor.js.org/config.md' --- # setupCache() The `setupCache` function receives the axios instance and a set of optional properties described below. This modifies the axios instance in place and returns it. ```ts const axios = setupCache(axiosInstance, OPTIONS); ``` ::: tip The `setupCache` function receives global options and all [request specifics](./config/request-specifics.md) ones too. This way, you can customize the defaults for all requests. ::: ::: tip If you want to use the same cache interceptor for all your axios instances, you can call `setupCache` with the default axios instance. ```ts import Axios from 'axios'; setupCache(Axios, OPTIONS); ``` ::: ## location * Type: `InstanceLocation` * Default: `typeof window === 'undefined' ? 'server' : 'client'` A hint to the library about where the axios instance is being used. Used to take some decisions like handling or not `Cache-Control: private`. ```ts // NodeJS const cache = setupCache(Axios.create(), { location: 'server' }); // Browser const cache = setupCache(Axios.create(), { location: 'client' }); ``` ## storage * Type: `AxiosStorage` * Default: `buildMemoryStorage()` A storage interface is the entity responsible for saving, retrieving and serializing data received from the network when an axios call is made. See the [Storages](./guide/storages.md) page for more information. ## generateKey * Type: `KeyGenerator` * Default: `defaultKeyGenerator` The `generateKey` property defines the function responsible for generating unique keys for each request cache. By default, it employs a strategy that prioritizes the `id` if available, falling back to a string generated using various request properties. The default implementation generates a 32-bit hash key using the `method`, `baseURL`, `params`, `data`, and `url` of the request. ::: warning In any persistent cache scenario where hitting over 77K unique keys is a possibility, you should use a more robust hashing algorithm. [Read more](./guide/request-id.md#custom-generator) ::: ## waiting * Type: `Map>` * Default: `new Map` A simple object that will hold a promise for each pending request. Used to handle concurrent requests. You shouldn't change this property, but it is exposed in case you need to use it as some sort of listener or know when a request is waiting for others to finish. ## headerInterpreter * Type: `HeaderInterpreter` * Default: `defaultHeaderInterpreter` The function used to interpret all headers from a request and determine a time to live (`ttl`) number. ::: warning Many REST backends return some variation of `Cache-Control: no-cache` or `Cache-Control: no-store` headers, which tell us to ignore caching at all. You shall disable `headerInterpreter` for those requests. *If the debug mode prints `Cache header interpreted as 'dont cache'` this is probably the reason.* ::: The possible returns are: * `'dont cache'`: the request will not be cached. * `'not enough headers'`: the request will find other ways to determine the TTL value. * `number`: used as the TTL value. * `{ cache: number, stale: number }`: used as the TTL value and stale TTL value ::: details Example of a custom headerInterpreter ```ts import { setupCache, type HeaderInterpreter } from 'axios-cache-interceptor'; const myHeaderInterpreter: HeaderInterpreter = (headers) => { if (headers['x-my-custom-header']) { const seconds = Number(headers['x-my-custom-header']); if (seconds < 1) { return 'dont cache'; } return seconds; } return 'not enough headers'; }; ``` ::: ## requestInterceptor * Type: `AxiosInterceptor>` * Default: `defaultRequestInterceptor()` The function that will be used to intercept the request before it is sent to the axios adapter. It is the main function of this library, as it is the bridge between the axios request and the cache. It wasn't meant to be changed, but if you need to, you can do it by passing a new function to this property. See its code for more information [here](https://github.com/arthurfiorette/axios-cache-interceptor/tree/main/src/interceptors). ## responseInterceptor * Type: `AxiosInterceptor>` * Default: `defaultResponseInterceptor()` The function that will be used to intercept the request after it is returned by the axios adapter. It is the second most important function of this library, as it is the bridge between the axios response and the cache. It wasn't meant to be changed, but if you need to, you can do it by passing a new function to this property. See its code for more information [here](https://github.com/arthurfiorette/axios-cache-interceptor/tree/main/src/interceptors). ## register * Type: `boolean` * Default: `true` Controls whether cache interceptors are automatically registered during `setupCache()`. * `true`: register both cache interceptors (default). * `false`: do not register cache interceptors automatically. Use `false` when you need full control over interceptor registration order. ```ts import Axios from 'axios'; import { setupCache } from 'axios-cache-interceptor'; const axios = setupCache(Axios.create(), { register: false }); // register your own interceptors first axios.interceptors.request.use((req) => req); axios.interceptors.response.use((res) => res); // then register cache interceptors manually axios.interceptors.request.use( axios.requestInterceptor.onFulfilled, axios.requestInterceptor.onRejected ); axios.interceptors.response.use( axios.responseInterceptor.onFulfilled, axios.responseInterceptor.onRejected ); ``` ## debug * Type: `(msg: { id?: string; msg?: string; data?: unknown }) => void` or `undefined` * Default: `undefined` ::: warning This option only works when targeting a [Development](./guide/debugging.md) build. ::: The debug option will print debug information in the console. It is good if you need to trace any undesired behavior or issue. You can enable it by setting `debug` to a function that receives a string and returns nothing. Read the [Debugging](./guide/debugging.md) page for the complete guide. ::: details Example of a custom debug function ```ts // Will print debug info in the console. setupCache(axiosInstance, { debug: console.log }); // Own logging platform. setupCache(axiosInstance, { debug: ({ id, msg, data }) => myLoggerExample.emit({ id, msg, data }) }); // Disables debug. (default) setupCache(axiosInstance, { debug: undefined }); ``` ::: --- --- url: 'https://axios-cache-interceptor.js.org/guide/storages.md' --- # Storages Storage adapters save and retrieve cache entries. They can also serialize entries when the underlying storage requires it. You can use a built-in adapter, create your own, or install one published on npm. A storage adapter connects the cache interceptor to a persistent or in-memory data store. The interceptors call it automatically, and you can also access the configured storage through `axios.storage` when you need to inspect or invalidate entries manually. Currently, two storages are included in the library by default: * [Memory Storage](#memory-storage), created with `buildMemoryStorage` (Node.js and web) * [Web Storage API](#web-storage-api), created with `buildWebStorage` (web only) ## Concurrent requests Concurrent request deduplication is local to each cache instance. When an eligible request is tracked in an instance's in-memory waiting map, later requests for the same key can wait for its result instead of reaching the network. Overrides and Vary mismatches can still produce additional requests. Shared storage, such as Redis, shares completed cache entries but does not share that in-memory coordination. 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 ::: warning **This is the storage chosen by default** ::: 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](https://github.com/arthurfiorette/axios-cache-interceptor/issues/163) and many similar reports. For long-running processes, use `cleanupInterval`, `maxEntries`, and `maxStaleAge` to bound memory usage and remove old entries. The storage uses a JavaScript `Map` internally for efficient key-value lookups and iteration. ```ts 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 by `get()` when set to `true`. Use `'double'` to also clone values before saving them. The default is `false`. * **cleanupInterval**: How often, in milliseconds, to remove old entries. Set it to `false` to disable automatic cleanup. The default is 5 minutes (`300_000`). * **maxEntries**: The maximum number of entries to retain. The storage uses a FIFO-based eviction order because entry sizes cannot be determined reliably. Set it to `false` to disable the limit. The default is `1024`. * **maxStaleAge**: How long, in milliseconds, a stale entry with a defined TTL can remain before removal. The default is 1 hour (`3_600_000`). ## Web Storage API Use `buildWebStorage` to preserve cached entries across page refreshes. It connects the cache storage API to the browser's [Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Storage). ::: code-group ```ts{7} [Local Storage] import axios from 'axios'; import { setupCache, buildWebStorage } from 'axios-cache-interceptor'; setupCache(axios, { // [!code focus:5] // As localStorage is a public storage, you can add a prefix // to all keys to avoid collisions with other code. storage: buildWebStorage(localStorage, 'axios-cache:') }); ``` ```ts{7} [Session Storage] import axios from 'axios'; import { setupCache, buildWebStorage } from 'axios-cache-interceptor'; setupCache(axios, { // [!code focus:5] // As sessionStorage is a public storage, you can add a prefix // to all keys to avoid collisions with other code. storage: buildWebStorage(sessionStorage, 'axios-cache:') }); ``` ```ts{4,7} [Custom Storage] import axios from 'axios'; import { setupCache, buildWebStorage } from 'axios-cache-interceptor'; const myStorage = new Storage(); // [!code focus:8] setupCache(axios, { storage: buildWebStorage( myStorage, 'axios-cache:', // prefix 60 * 60 * 1000 // maxStaleAge (1 hour default) ) }); ``` ::: Options: * **storage**: The `Storage` instance to use, such as `localStorage` or `sessionStorage`. * **prefix**: A prefix added to every key to avoid collisions. The default is `'axios-cache-'`. * **maxStaleAge**: How long, in milliseconds, a stale entry can remain before removal. The default is 1 hour (`3_600_000`). ### Browser quota From `v0.9.0` onwards, web storage is able to detect and evict older entries if the browser's quota is reached. The storage handles quota errors as follows: 1. Try to save the value. 2. If the quota is exceeded, remove expired entries that cannot become stale. 3. Retry the write. If it still fails, remove the oldest entry with the configured prefix. 4. Repeat until the write succeeds or no matching entries remain. 5. 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`. Use the same function to create a custom adapter from the following methods: * `set(key: string, value: NotEmptyStorageValue, currentRequest?: CacheRequestConfig): MaybePromise`: Saves a cache value under the given key. * `remove(key: string, currentRequest?: CacheRequestConfig): MaybePromise`: Removes the value stored under the given key. * `find(key: string, currentRequest?: CacheRequestConfig) => MaybePromise`: Returns the stored value, or `undefined` when the key does not exist. * `clear() => MaybePromise`: Optionally clears all stored data. The interceptor does not call this method; it is available for application-level invalidation. ## Third-party storages The following examples are not maintained as separate integrations. Use them as starting points and adapt their expiration and serialization behavior to your application. * [Node Redis v4](#node-redis-storage) * [IndexedDB](#indexeddb) * [Node Cache](#node-cache) * [Open a pull request](https://github.com/arthurfiorette/axios-cache-interceptor/pulls) 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. ```ts 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': if (!canStale(value)) { return value.createdAt + value.ttl; } break; } // Keep revalidatable entries for at most one hour by default. 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`](#buildstorage) to integrate other systems such as localForage, ioredis, or Memcached. ## IndexedDB This example uses `idb-keyval` to store cache entries in IndexedDB. ```ts 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](https://github.com/node-cache/node-cache). Check the package's current maintenance status before using it in a new application. ```ts 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); } }); ```