axios-cache-interceptor

repository·main·Indexed 21 days ago

https://github.com/arthurfiorette/axios-cache-interceptor

A performance-oriented interceptor for Axios that handles HTTP request caching to avoid redundant network calls. It provides features such as custom storage engines, configurable Time To Live (TTL), request-specific cache overrides, and support for non-GET methods. The library includes a setupCache function to initialize caching and supports both opt-in and opt-out caching patterns.

Tokens
20.7K
Snippets
53
Records
80
Agent score
74%

What's inside axios-cache-interceptor

  1. Key features of Axios Cache Interceptor

    main

    Axios Cache Interceptor provides several built-in capabilities for managing network requests:

    • Cache Validation: Supports TTL (Time To Live), Cache-Control, and ETag.
    • Resilience: Can return a previous cached request if a new network request fails.
    • Concurrency: Handles parallel requests for the same resource efficiently.
    • Storage Options: Includes built-in storage engines for In-Memory, Local Storage, and Session Storage.
    • Customization: 100% customizable logic and storage.
    • Performance: Lightweight (< 4.3Kb minified/gzipped) and optimized for speed (up to 22x faster than standard axios usage in certain scenarios).
  2. Understand interceptor execution order

    main

    When using axios-cache-interceptor alongside other interceptors, the execution order depends on when the interceptors are registered relative to setupCache().

    Axios follows two different patterns for interceptors:

    • Request interceptors use LIFO (Last In, First Out): The last interceptor added is the first one to run.
    • Response interceptors use FIFO (First In, First Out): The first interceptor added is the first one to run.

    Default Behavior with setupCache():

    • Requests: Interceptors registered before setupCache() run after the cache interceptor. Interceptors registered after setupCache() run before the cache interceptor.
    • Responses: Interceptors registered before setupCache() run before the cache interceptor. Interceptors registered after setupCache() run after the cache interceptor.
    // 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);
  3. How Request IDs work in axios-cache-interceptor

    main

    The library distinguishes requests by assigning a unique id to each request. These IDs serve as the cache keys in the underlying storage.

    An ID performs three main functions:

    1. Binding: It binds a specific cache entry to a request.
    2. Referencing/Invalidation: It allows you to reference or invalidate a specific cache entry later.
    3. Deduplication: It ensures the interceptor uses the same cache for requests directed at the same endpoint and parameters.

    The default ID generator is designed to normalize requests. For example, { baseURL: 'https://a.com/', url: '/b' } will produce the same ID as { url: 'https://a.com/b/' }.

  4. Configure cache.cacheTakeover to prevent double caching

    main

    When a server sends Cache-Control headers, both the library and the browser might cache the response, creating a double layer of cache.

    Setting cache.cacheTakeover: true (the default) prevents this by adding headers to the request (Cache-Control: no-cache, no-store, must-revalidate, max-age=0, Pragma: no-cache, and Expires: 0) that instruct the browser/adapter not to cache the response, ensuring only the library manages the cache.

    CORS Warning: This will fail on CORS requests if the server does not include Cache-Control, Pragma, and Expires in its Access-Control-Allow-Headers configuration. If you cannot modify CORS, either set cacheTakeover: false or use a unique query parameter (cache buster) to bypass browser caching.

    // Alternative: Using a cache buster for maximum reliability
    axios.get(
      `/api/data?cachebuster=${Math.random().toString(36).slice(2)}`,
      { id: 'api-data-endpoint' } // Keep same cache key despite different URLs
    );
  5. How Axios Cache Interceptor works

    main

    Axios Cache Interceptor uses axios interceptors rather than adapters to provide a minimally invasive caching layer. This approach allows you to continue using your preferred axios adapter while the interceptor manages the cache lifecycle.

    Request Lifecycle:

    1. Interceptor Check: Before the request reaches the adapter, the interceptor checks if the request is already cached and valid, if it should be cached, or if there is an identical request currently in flight that can be awaited.
    2. Adapter Execution: If no valid cache is found, the request proceeds to the axios adapter for the actual network call.
    3. Response Handling: After the adapter returns a response, the interceptor checks if the request is cacheable. If so, it saves the response to storage and resolves any other pending requests waiting for that same resource.
  6. How storage adapters work

    main

    Storage adapters are responsible for saving, retrieving, and serializing cache entries. They connect the cache interceptor to a persistent or in-memory data store.

    Key behaviors:

    • Automatic usage: The interceptors call the adapter automatically during the request lifecycle.
    • Manual access: You can access the configured storage via axios.storage to manually inspect or invalidate entries.
    • Concurrency: Request deduplication (waiting for an in-flight request instead of hitting the network) is local to each cache instance. Shared storages like Redis share completed entries but do not share the in-memory coordination required for distributed request deduplication. If two processes request the same uncached key simultaneously, both may send a network request.
  7. Quickstart: Setup and use Axios Cache Interceptor

    main

    To use axios-cache-interceptor, create a standard Axios instance and pass it to the setupCache function. This returns a new Axios instance equipped with caching capabilities. When making requests, you can check the cached property on the response object to determine if the data was served from the cache or fetched from the network.

    import Axios from 'axios';
    import { setupCache } from 'axios-cache-interceptor';
    
    const instance = Axios.create();
    const axios = setupCache(instance);
    
    const req1 = axios.get('https://arthur.place/');
    const req2 = axios.get('https://arthur.place/');
    
    const [res1, res2] = await Promise.all([req1, req2]);
    
    console.log(res1.cached); // false
    console.log(res2.cached); // true
  8. Invalidate cache programmatically using `cache.update`

    main

    When a mutation (like a POST or PUT request) is simple, you can update the existing cache entry directly using the cache.update option. This avoids an extra network request by manually applying the changes to the cached data.

    To use this:

    1. Assign a unique id to the request you want to update (e.g., id: 'list-posts').
    2. In your mutation request, provide a cache.update object where the key is the id of the target cache entry.
    3. The value should be a function: (targetCache, mutationResponse) => updatedCache | 'ignore'.

    Important for Vue users: If you are modifying arrays within the cache, ensure you create a copy of the array (e.g., using the spread operator [...]) before modifying it to ensure reactivity works correctly in the UI.

    // 1. Define the target request with a specific ID
    function listPosts() {
      return axios.get('/posts', {
        id: 'list-posts'
      });
    }
    
    // 2. Perform mutation and update the target cache
    function createPost(data) {
      return axios.post(
        '/posts',
        data,
        {
          cache: {
            update: {
              // Key is the ID of the cache entry to update
              'list-posts': (listPostsCache, createPostResponse) => {
                // Only update if the entry is actually in a 'cached' state
                if (listPostsCache.state !== 'cached') {
                  return 'ignore';
                }
    
                // For Vue reactivity: copy the array before pushing
                listPostsCache.data.posts = [...listPostsCache.data.posts];
                listPostsCache.data.posts.push(createPostResponse.data);
    
                return listPostsCache;
              }
            }
          }
        }
      );
    }
  9. Configure Web Storage API (Browser)

    main

    Use buildWebStorage to persist cache entries across page refreshes using the browser's Storage API. This is for web environments only.

    Browser Quota Handling: If the browser's storage quota is reached, the adapter will attempt to evict expired entries or the oldest entries with the configured prefix to make room for new writes. If the write still fails, the value remains unstored.

    // Local Storage
    import axios from 'axios';
    import { setupCache, buildWebStorage } from 'axios-cache-interceptor';
    
    setupCache(axios, {
      storage: buildWebStorage(localStorage, 'axios-cache:')
    });
    
    // Session Storage
    setupCache(axios, {
      storage: buildWebStorage(sessionStorage, 'axios-cache:')
    });
    
    // Custom Storage instance
    const myStorage = new Storage();
    setupCache(axios, {
      storage: buildWebStorage(
        myStorage,
        'axios-cache:', // prefix
        60 * 60 * 1000  // maxStaleAge
      )
    });
  10. Manually register cache interceptors for full control

    main

    If you need specific control over the execution order, you can disable automatic registration by passing { register: false } to setupCache(). You must then manually register the cache interceptors using the properties provided on the axios instance.

    To ensure correct behavior:

    1. Register the cache response interceptor first (since response interceptors are FIFO).
    2. Register your custom interceptors.
    3. Register the cache request interceptor last (since request interceptors are LIFO).
    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
    );