← Back to all snippets
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.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs