← Back to all snippets
JAVASCRIPT

Client-Side API Response Caching with IndexedDB

Enhance web application performance and offline capabilities by caching API responses securely and persistently using the browser's IndexedDB.

const DB_NAME = 'apiCacheDB';
const DB_VERSION = 1;
const STORE_NAME = 'apiResponses';

let db;

async function openDatabase() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION);

    request.onerror = (event) => {
      console.error('IndexedDB error:', event.target.errorCode);
      reject('IndexedDB error');
    };

    request.onupgradeneeded = (event) => {
      db = event.target.result;
      if (!db.objectStoreNames.contains(STORE_NAME)) {
        const objectStore = db.createObjectStore(STORE_NAME, { keyPath: 'url' });
        objectStore.createIndex('timestamp', 'timestamp', { unique: false });
      }
    };

    request.onsuccess = (event) => {
      db = event.target.result;
      resolve(db);
    };
  });
}

async function getCachedResponse(url, maxAgeMs = 3600000) { // 1 hour default cache
  if (!db) await openDatabase();

  return new Promise((resolve) => {
    const transaction = db.transaction([STORE_NAME], 'readonly');
    const store = transaction.objectStore(STORE_NAME);
    const request = store.get(url);

    request.onsuccess = (event) => {
      const cachedData = event.target.result;
      if (cachedData && (Date.now() - cachedData.timestamp < maxAgeMs)) {
        console.log('Cache hit for:', url);
        resolve(cachedData.data);
      } else {
        console.log('Cache miss or expired for:', url);
        resolve(null); // Cache miss or expired
      }
    };

    request.onerror = (event) => {
      console.error('Error getting cached data:', event.target.errorCode);
      resolve(null);
    };
  });
}

async function setCachedResponse(url, data) {
  if (!db) await openDatabase();

  return new Promise((resolve, reject) => {
    const transaction = db.transaction([STORE_NAME], 'readwrite');
    const store = transaction.objectStore(STORE_NAME);
    const request = store.put({ url, data, timestamp: Date.now() });

    request.onsuccess = () => {
      console.log('Data cached for:', url);
      resolve();
    };

    request.onerror = (event) => {
      console.error('Error setting cached data:', event.target.errorCode);
      reject('Error setting cached data');
    };
  });
}

async function fetchAndCache(url, options = {}, maxAgeMs = 3600000) {
  const cached = await getCachedResponse(url, maxAgeMs);
  if (cached) {
    return cached;
  }

  const response = await fetch(url, options);
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  const data = await response.json();
  await setCachedResponse(url, data);
  return data;
}

// Example usage:
// fetchAndCache('https://api.example.com/products', { headers: { 'Accept': 'application/json' } }, 60 * 60 * 1000) // Cache for 1 hour
//   .then(data => console.log('Fetched (potentially cached) products:', data))
//   .catch(error => console.error('Error:', error));
How it works: This JavaScript snippet provides a client-side caching mechanism for API responses using `IndexedDB`. It wraps the standard `fetch` API, first attempting to retrieve data from a dedicated IndexedDB object store. If a valid, non-expired cache entry is found, it returns the cached data instantly. Otherwise, it proceeds with a network request, caches the fresh response along with a timestamp, and then returns it. This approach significantly improves performance, reduces network requests, and enhances the application's offline capabilities by storing larger datasets persistently than `localStorage`.

Need help integrating this into your project?

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

Hire DigitalCodeLabs