Cache API 101
A cache is a form of storage for “requests” and “responses”. When you visited this page, you made a request for /cache-api-101
. The response to that request was the HTML document you are viewing in your browser right now. The cache API allows us to store these requests and responses locally in the browser.
The cache API was created as part of the service worker specification to “allow authors to fully manage their content caches for offline use”. In my tutorial, Going Offline First, I demonstrated how I used the cache API to convert this blog to an offline-first strategy, enabling readers to save articles for offline. If you want to see a real-world use case of cache, I would suggest you watch that.
In this article, I want to go over the cache API terminology and how to use the API methods.
Cache storage organisation and terminology #
Before we dive into the specifics of how to create and use a cache, let’s go over how the cache storage is organised and what the different bits are called.
If you open up your browser developer tools, you will see a section for “Cache Storage”.
- Firefox: Storage > Cache Storage
- Google Chrome: Application > Cache > Cache Storage
Each origin, e.g. for this website https://bitsofco.de
, has one cache storage. This object can contain multiple cache objects, each containing a request-response list.
Let’s take this in reverse. An example of a request-response could be, as I mentioned at the start of this article, this page’s URL (the request) and the HTML content (the response). We can save this request-response in a cache object that I call “articles-cache”. This “articles-cache” cache would belong to the cache storage for the https://bitsofco.de
origin.
CRUD with the cache API #
Like any form of storage, we need to be able to perform create, read, update, and delete operations to use it. To illustrate this, let’s create a cache I will call “my-cache”, which will store a page, /subscribe
, and an image, /assets/images/profile.png
.
Creating a new cache and adding items #
To add a request-response item to a cache, we need to first create the cache to add to it. As I mentioned, an origin can have multiple caches within the cache storage, so we need to specify which cache we want to add the request-response item to.
The method for creating and/or accessing a cache is caches.open()
. The caches
name is a global variable that refers to the cache storage object. The open()
method accepts one argument, the name of the cache we want to access.
caches.open("my-cache").then((myCache) => {
// Do stuff with myCache
});
To add a request-response item to a cache, we use the add()
method on the cache object returned. This method accepts a request, which can either be a full request object, or just a URL.
caches.open("my-cache").then((myCache) => {
// URL only
myCache.add("/subscribe");
// Full request object
myCache.add(new Request('/subscribe', {
method: "GET",
headers: new Headers({
'Content-Type': 'text/html'
}),
/* more request options */
});
});
The browser will make the fetch request given the specified Request
, and save the response to the cache.
If we have multiple items to add, we can use the addAll()
method, which accepts an array of requests.
caches.open("my-cache").then((myCache) => {
myCache.addAll([
"/subscribe",
"/assets/images/profile.png"
]);
});
Reading items from cache storage #
To find an item from the cache storage, we can use the caches.match()
method.
caches.match("/subscribe").then((cachedResponse) => {
if (cachedResponse) {
// Do something with cachedResponse
} else {
// Handle if response not found
}
});
We can also look within a particular cache with the same method on the cache
object. This is helpful if we have multiple cache objects and we know that an item may exist in a particular one.
caches.open("my-cache").then((myCache) => {
myCache.match("/subscribe").then((cachedResponse) => {
if (cachedResponse) {
// Do something with cachedResponse
} else {
// Handle if response not found
}
});
});
Similar to the addAll()
method, we also have a matchAll()
method, which will return an array of cached responses. This is useful for searching for all items that match a URL fragment, e.g. any items with /images/
in their path.
caches.open("my-cache").then((myCache) => {
myCache.matchAll("/images/").then((cachedResponses) => {
if (cachedResponses) {
// Do something with cachedResponses
} else {
// Handle if response not found
}
});
});
Updating an item in the cache #
Next, we may want to update a request-response item in the cache. Let’s say, for example, the content of the /subscribe
page has changed and we want to update what is in the cache with the new version.
We can do this in one of two ways. First, we can call the same, cache.add()
method. This will first perform a fetch for the request page and replace any request-response item in the cache that has the same URL.
caches.open("my-cache").then((myCache) => {
// browser will request and cache the response
myCache.add("/subscribe");
});
Alternatively, if we want more control over what response is put in the cache, we can use the cache.put()
method. Using this method, we need to specify both a request and a response. This means we will need to fetch the document ourselves first.
const request = new Request("/subscribe");
fetch(request).then((fetchResponse) => {
caches.open('my-cache').then((myCache) => {
cache.put(request, fetchResponse);
});
});
Deleting an item from the Cache #
Finally, we need to be able to remove a request-response item from the cache or delete a cache altogether.
To remove a specific item from the Cache, we use the cache.delete()
method, passing in the request URL.
caches.open("my-cache").then((myCache) => {
myCache.delete("/subscribe");
});
To remove a cache altogether, we can use the same method on the caches
object.
caches.delete("my-cache");
Where to use the cache API #
Although the cache API was created with the Service Worker, it can be used in the main document as well.
The caches
object is available to the window
. We can check if it exists in the current browser before using it.
if ('caches' in window) {
caches.open("my-cache").then((myCache) => {
// Do stuff
});
}
In our service worker file, we can use the same cache API to intercept fetch requests and return cached responses if they exist.
self.addEventListener('fetch', (e) => {
e.respondWith(
// Check if item exists in cache
caches.match(e.request).then((cachedResponse) => {
// If found in cache, return cached response
if (cachedResponse) return cachedResponse;
// If not found, fetch over network
return fetch(e.request);
});
);
});