JAVASCRIPT
Client-Side API Caching with IndexedDB
Implement persistent client-side caching of API responses using IndexedDB to improve performance, reduce network requests, and support offline capabilities in web applications.
class IndexedDBCache {
constructor(dbName = 'api-cache', storeName = 'responses', version = 1) {
this.dbName = dbName;
this.storeName = storeName;
this.version = version;
this.db = null;
}
async openDB() {
return new Promise((resolve, reject) => {
if (this.db) {
return resolve(this.db);
}
const request = indexedDB.open(this.dbName, this.version);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName, { keyPath: 'url' });
}
};
request.onsuccess = (event) => {
this.db = event.target.result;
resolve(this.db);
};
request.onerror = (event) => {
console.error('IndexedDB error:', event.target.error);
reject(event.target.error);
};
});
}
async get(url) {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.get(url);
request.onsuccess = (event) => resolve(event.target.result);
request.onerror = (event) => reject(event.target.error);
});
}
async set(url, data, ttl = 3600000) { // Default TTL 1 hour (in ms)
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const record = { url, data, timestamp: Date.now(), ttl };
const request = store.put(record);
request.onsuccess = () => resolve();
request.onerror = (event) => reject(event.target.error);
});
}
async invalidate(url) {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.delete(url);
request.onsuccess = () => resolve();
request.onerror = (event) => reject(event.target.error);
});
}
async fetchAndCache(url, options = {}, cacheTTL = 3600000) {
// Try to get from cache first
const cached = await this.get(url);
if (cached && (Date.now() - cached.timestamp < cached.ttl)) {
console.log(`Serving ${url} from cache.`);
return cached.data;
}
console.log(`Fetching ${url} from network...`);
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
await this.set(url, data, cacheTTL); // Cache the new data
return data;
}
}
// Example Usage:
// const apiCache = new IndexedDBCache();
// (async () => {
// try {
// // First fetch will hit network and cache
// const data1 = await apiCache.fetchAndCache('https://jsonplaceholder.typicode.com/posts/1', {}, 60000);
// console.log('API Data 1:', data1);
// // Second fetch (within 60 seconds) will hit cache
// const data2 = await apiCache.fetchAndCache('https://jsonplaceholder.typicode.com/posts/1', {}, 60000);
// console.log('API Data 2 (cached):', data2);
// // Invalidate a specific cache entry
// // await apiCache.invalidate('https://jsonplaceholder.typicode.com/posts/1');
// // console.log('Cache invalidated for post 1. Next fetch will be from network.');
// } catch (error) {
// console.error('Caching error:', error);
// }
// })();
How it works: This JavaScript snippet provides a class, `IndexedDBCache`, for robust client-side caching of API responses using `IndexedDB`. `IndexedDB` is a powerful, persistent storage solution ideal for larger amounts of structured data, surpassing `localStorage`'s capabilities. The class handles opening and upgrading the database, and provides methods to `get`, `set`, and `invalidate` cached data. The `fetchAndCache` method encapsulates the logic to first check the cache for a valid (non-expired) entry before making a network request. If a fresh response is fetched, it's stored in `IndexedDB` with a configurable Time-To-Live (TTL). This pattern significantly improves application performance, reduces server load, and can even facilitate offline data access.