Create a method to Cache multiple API calls

In modern web development, optimizing performance and reducing server load is crucial. One effective strategy is to cache API responses, so repeated requests for the same data can be served quickly without hitting the server multiple times. In this blog, we’ll walk through creating a functional API call cache in JavaScript. This is created using Closures.

Why Cache API Calls?

  1. Improved Performance: Cached responses are served faster than network requests.
  2. Reduced Server Load: Decreases the number of requests to the server.
  3. Offline Capability: Cached data can be used when the network is unavailable.

Step-by-Step Implementation to Cache Multiple API Calls

  1. Initialize the Cache Storage: Use a closure to store the cache.
  2. Generate Unique Cache Keys: Create unique keys based on the API endpoint and options.
  3. Check the Cache: Before making an API call, check if the response is already cached.
  4. Fetch and Cache Responses: Make the API call if the response is not cached, then store the response.
  5. Return Cached Response: If the response is cached, return it immediately.
const createApiCache = () => {
  const cache = new Map();

  const generateCacheKey = (url, options) => {
    return `${url}:${JSON.stringify(options)}`;
  };

  const fetchWithCache = (url, options = {}) => {
    const cacheKey = generateCacheKey(url, options);

    // Check if response is in cache
    if (cache.has(cacheKey)) {
      return Promise.resolve(cache.get(cacheKey));
    }

    // Make API call if not cached
    return fetch(url, options)
      .then(response => response.json())
      .then(data => {
        cache.set(cacheKey, data);
        return data;
      });
  };

  const clearCache = () => {
    cache.clear();
  };

  return {
    fetchWithCache,
    clearCache,
  };
};

// Usage example
const apiCache = createApiCache();

apiCache.fetchWithCache('https://api.example.com/data')
  .then(data => {
    console.log('Data from API:', data);
  })
  .catch(error => {
    console.error('API call failed:', error);
  });

Explanation

  • createApiCache: Initializes a cache (Map) and returns functions to interact with it.
  • generateCacheKey: Generates a unique key based on the URL and options.
  • fetchWithCache: Checks the cache before making an API call and caches the response.
  • clearCache: Clears the cache.

If we want to create a Persistent Cache then we can use localStorage. Also, in some cases interviewers expect you to create a caching mechanism with expiry.

Improved Caching API Calls with Expiry

const createCachedAPICall = (cacheDuration) => {
  const cache = {};

  const generateKey = (path, config) => {
    return `${path}:${JSON.stringify(config)}`;
  };

  return async function (path, config = {}) {
    const key = generateKey(path, config);
    const currentTimestamp = Date.now();

    if (!cache[key] || currentTimestamp > cache[key].expiry) {
      try {
        const response = await fetch(path, config);
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const data = await response.json();
        cache[key] = { data, expiry: currentTimestamp + cacheDuration };
      } catch (error) {
        console.error('Fetch error:', error);
        throw error;
      }
    }

    return cache[key].data;
  };
};

// Usage example
const cachedAPICall = createCachedAPICall(1500);

cachedAPICall('https://jsonplaceholder.typicode.com/todos/1')
  .then(data => {
    console.log('Data:', data);
  })
  .catch(error => {
    console.error('Error:', error);
  });

Explanation

  1. createCachedAPICall: Initializes the cache and sets up the cache duration.
    • cacheDuration: Time in milliseconds before a cached response expires.
  2. generateKey: Creates a unique key for each API request based on the URL and configuration.
    • This ensures that different requests to the same endpoint with different configurations are cached separately.
  3. Cached Function: The returned function performs the following steps:
    • Generates a unique cache key.
    • Check if the cached data exists and is still valid (Cache can be a map or Object or localStorage)
    • Makes an API call if the data is not cached or expired.
    • Caches the response with an expiry time.
    • Returns the cached data.