DEV Community

Adam Miller
Adam Miller

Posted on

How To Add a Cache Layer to your JavaScript API

Caching can be difficult to get right, but it doesn't really take much code if you are aware of the gotcha's. The sample below implements a simple cache around an API in roughly 10 lines of code:

      const apiCache = new Map();
      async function apiWrapper(url) {
        if (!apiCache.has(url)) {
          const promise = fetch(url).then((r) => r.json());
          apiCache.set(url, promise);
        }

        const response = await apiCache.get(url);
        return cloneDeep(response);
      }

There are a few key points to note with the code above.

It always returns a promise.

You don't want to cache the result, you want to cache the promise. This handles race conditions where multiple callers are requesting the same resource simultaneously. The consuming code behaves the same whether its the 1st call for this resource or the 20th. The promise just resolves more quickly when the resource has already been cached.

The response is cloned before returning to the consumer.

Because Javascript will return a reference, if a caller modifies the response, it will be modified for all future consumers. This has to be a deep clone, using a shallow clone via object spread or assign will cause issues. Use cloneDeep from lodash.

Note that this implementation uses the URL as the cache key. If your api uses http body with a POST, you will want to serialize the data and add it to the url to build a unique key.

Below is a full standalone html page if you want to try it out and verify edge cases:

<html>
  <head> </head>
  <body>
    <script>
      function cloneDeep(obj) {
        //example only, use lodash
        return JSON.parse(JSON.stringify(obj));
      }

      const apiCache = new Map();
      async function apiWrapper(url) {
        if (!apiCache.has(url)) {
          const promise = fetch(url).then((r) => r.json());
          apiCache.set(url, promise);
        }

        const response = await apiCache.get(url);
        return cloneDeep(response);
      }

      const post1Url = "https://jsonplaceholder.typicode.com/posts/1";
      console.log("make 1st request");
      apiWrapper(post1Url)
        .then((post1) => {
          post1.title = "fetched from 1";
          console.log(post1);
        })
        .catch((e) => {
          console.log(e);
        });

      console.log("make 2nd request");
      apiWrapper(post1Url)
        .then((post1) => {
          console.log(post1);
        })
        .catch((e) => {
          console.log(e);
        });
    </script>
  </body>
</html>

Top comments (0)