Cache Storage API — Storing and Retrieving Network Responses
In this tutorial, you will learn about Cache Storage API. We cover key concepts, practical examples, and best practices to help you master this topic.
The Cache Storage API stores HTTP Request/Response pairs in the browser, enabling offline access, faster loads, and programmable Caching strategies for Progressive Web Apps.
What You'll Learn
By the end of this tutorial, you will understand how to open caches, add and retrieve responses, delete entries, and manage cache storage limits programmatically.
Why It Matters
The Cache Storage API is the foundation of all PWA caching strategies. Without understanding how it works, you cannot debug cache issues, optimize storage usage, or implement advanced strategies like cache pruning or quota management.
Real-World Use
A news PWA uses multiple caches: a static cache for the app shell, a dynamic cache for article responses, and an images cache. Each cache has a size limit. When the images cache exceeds 50 entries, the oldest 10 are evicted. The user never sees stale images or storage warnings.
What Is Cache Storage
Cache Storage is a browser storage mechanism specifically designed for HTTP Request/Response objects. It is different from localStorage (which stores strings) and IndexedDB (which stores structured data).
Cache Storage vs Other Storage
┌──────────────────────────────────────────────────────────┐
│ Browser Storage Options │
├────────────┬──────────────┬──────────────┬──────────────┤
│ │ Cache API │ IndexedDB │ localStorage │
├────────────┼──────────────┼──────────────┼──────────────┤
│ Data │ HTTP pairs │ Structured │ Strings │
│ Size │ Large │ Large │ ~5-10MB │
│ Async │ Yes │ Yes │ No │
│ Indexed │ By URL │ By key/index │ By key │
│ Expiry │ Manual │ Manual │ Manual │
│ Use case │ Offline │ App data │ Preferences │
└────────────┴──────────────┴──────────────┴──────────────┘
Think of Cache Storage like a filing cabinet specifically designed for network responses. Each drawer (cache) is labeled, and each file (response) is organized by URL (request). You open a drawer, find a file by URL, read it, or put a new file in.
Opening and Creating Caches
// Open a cache (creates if it does not exist)
caches.open('my-cache-v1').then(cache => {
console.log('Cache opened:', cache);
});
// Check if a cache exists
caches.has('my-cache-v1').then(exists => {
console.log('Cache exists:', exists);
});
// List all caches
caches.keys().then(names => {
console.log('All caches:', names);
});
// Delete a cache
caches.delete('old-cache').then(deleted => {
console.log('Cache deleted:', deleted);
});
Output:
Cache opened: [object Cache]
Cache exists: true
All caches: ['my-cache-v1', 'my-cache-v0']
Cache deleted: true
Adding Responses to Cache
There are three ways to add responses to a cache:
// Method 1: cache.add(url) — fetches the URL and caches the response
caches.open('dynamic-cache').then(cache => {
cache.add('/api/data.json').then(() => {
console.log('Fetched and cached: /api/data.json');
});
});
// Method 2: cache.addAll(urls) — fetches and caches multiple URLs
caches.open('static-cache').then(cache => {
cache.addAll([
'/styles/main.css',
'/scripts/app.js',
'/images/logo.png'
]).then(() => {
console.log('All static assets cached');
});
});
// Method 3: cache.put(request, response) — stores an existing response
caches.open('dynamic-cache').then(cache => {
fetch('/api/users').then(response => {
if (response.ok) {
cache.put('/api/users', response.clone());
console.log('API response cached');
}
});
});
Output:
Fetched and cached: /api/data.json
All static assets cached
API response cached
Retrieving Responses
// Match a single request
caches.open('my-cache').then(cache => {
cache.match('/styles/main.css').then(response => {
if (response) {
console.log('Found in cache:', response.url);
console.log('Status:', response.status);
} else {
console.log('Not in cache');
}
});
});
// Match across all caches
caches.match('/styles/main.css').then(response => {
if (response) {
console.log('Found in any cache');
}
});
// Get all cached requests
caches.open('my-cache').then(cache => {
cache.keys().then(requests => {
console.log(`Cache has ${requests.length} entries:`);
requests.forEach(request => {
console.log(' -', request.url);
});
});
});
Output:
Found in cache: https://example.com/styles/main.css
Status: 200
Found in any cache
Cache has 15 entries:
- https://example.com/
- https://example.com/styles/main.css
- https://example.com/scripts/app.js
...
Cache Quota Management
Browsers limit how much storage a single origin can use. You should monitor and manage your cache size:
// Check storage usage
async function checkStorageQuota() {
if ('storage' in navigator && 'estimate' in navigator.storage) {
const estimate = await navigator.storage.estimate();
const usageMB = (estimate.usage / (1024 * 1024)).toFixed(2);
const quotaMB = (estimate.quota / (1024 * 1024)).toFixed(2);
const percent = ((estimate.usage / estimate.quota) * 100).toFixed(1);
console.log(`Storage: ${usageMB}MB / ${quotaMB}MB (${percent}%)`);
return { usage: estimate.usage, quota: estimate.quota, percent };
}
}
checkStorageQuota().then(info => {
if (info && info.percent > 80) {
console.warn('Storage almost full, evicting old caches');
evictOldestCache();
}
});
// Evict the oldest cache when storage is low
function evictOldestCache() {
caches.keys().then(names => {
// Find the oldest cache by name convention (e.g., cache-v1, cache-v2)
const sorted = names.sort();
const oldest = sorted[0];
if (oldest) {
caches.delete(oldest).then(() => {
console.log('Evicted:', oldest);
});
}
});
}
Output:
Storage: 12.34MB / 100MB (12.3%)
Storage: 85.67MB / 100MB (85.7%)
Storage almost full, evicting old caches
Evicted: images-v1
Cache Entry Management
// Delete a specific entry from a cache
caches.open('dynamic-cache').then(cache => {
cache.delete('/api/stale-data.json').then(deleted => {
console.log('Entry deleted:', deleted);
});
});
// Prune cache to a maximum number of entries
async function pruneCache(cacheName, maxEntries) {
const cache = await caches.open(cacheName);
const keys = await cache.keys();
if (keys.length > maxEntries) {
const toDelete = keys.length - maxEntries;
console.log(`Pruning ${toDelete} entries from ${cacheName}`);
// Delete oldest entries (first in array)
for (let i = 0; i < toDelete; i++) {
await cache.delete(keys[i]);
}
}
}
// Prune when cache grows too large
pruneCache('dynamic-cache', 50);
Output:
Entry deleted: true
Pruning 10 entries from dynamic-cache
Cache Strategies with Cache API
Understanding Cache Storage lets you implement any caching Strategy:
// Stale-while-revalidate using Cache API directly
self.addEventListener('fetch', event => {
event.respondWith(
caches.open('dynamic-cache').then(cache => {
return cache.match(event.request).then(cachedResponse => {
const fetchPromise = fetch(event.request).then(networkResponse => {
// Update cache with fresh response
cache.put(event.request, networkResponse.clone());
return networkResponse;
}).catch(() => {
// Network failed, return cached version
return cachedResponse;
});
// Return cached immediately, or wait for network if no cache
return cachedResponse || fetchPromise;
});
})
);
});
Common Mistakes
- Using cache.match() instead of cache.match(request). cache.match() without arguments throws an error. Always pass a Request or URL string.
- Not cloning responses before caching. Response bodies are streams that can only be read once. Always call response.clone() before passing to cache.put().
- Forgetting to handle cache misses. cache.match() returns undefined if no match. Always check if the response exists before using it.
- Not checking response.ok before caching. Cache error responses (4xx, 5xx) will serve broken content. Cache only successful responses.
- Mixing cache.put() with cache.add() semantics. cache.add() fetches the URL. cache.put() stores a pre-fetched response. Do not use them interchangeably.
Practice Questions
- What is the difference between cache.add() and cache.put()?
- How do you check how much storage your caches are using?
- What happens when you exceed browser storage quota?
- Why must you clone a response before caching it?
- How do you delete a single entry from a cache?
Challenge: Write a function that monitors cache storage and automatically evicts the oldest 10% of entries when usage exceeds 80% of the quota. Test by adding many entries programmatically.
FAQ
Mini Project
Create a cache manager service worker that maintains three caches: static-v1 (for app shell), dynamic-v1 (for API responses, max 30 entries), images-v1 (for images, max 50 entries). Implement functions for each cache type: add, retrieve, delete, and prune. Log all operations to the console.
What's Next
You understand Cache Storage. Now explore the cache-first strategy, which serves cached content instantly and only falls back to the network when needed.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro