Featured image of post Replacing LocalStorage: Dynamic IndexedDB wrapper localForage Featured image of post Replacing LocalStorage: Dynamic IndexedDB wrapper localForage

Replacing LocalStorage: Dynamic IndexedDB wrapper localForage

Ensure large client storage allocations by implementing async IndexedDB engines simplified via localForage.

Introduction

Client-side storage is a fundamental requirement for modern web applications — offline caches, user preferences, draft data, and session state all need to persist in the browser. For years, localStorage has been the simplest go-to solution, but its limitations become painful as applications grow. This article compares localStorage, raw IndexedDB, and the localForage wrapper library, providing practical guidance for choosing the right storage engine.


The Limits of localStorage

localStorage offers a straightforward synchronous key-value API — setItem(key, value) and getItem(key) — but it comes with hard constraints:

ConstraintDetail
Maximum size~5 MB per origin (varies slightly across browsers)
Synchronous APIBlocks the main thread during read/write operations
String-only valuesRequires manual JSON serialization for objects
No indexingCannot query or filter stored data efficiently

In a single-page application that maintains a large offline cache or complex application state, hitting the 5 MB ceiling is common. Moreover, synchronous writes that block rendering degrade user experience, especially on slow devices.


IndexedDB: Powerful but Complex

IndexedDB is the browser’s low-level NoSQL database. It offers:

  • Virtually unlimited storage (browser-dependent, typically hundreds of MB to GB)
  • Fully asynchronous API that does not block the UI thread
  • Structured data support — objects, blobs, and arrays without serialization
  • Indexed queries for efficient data retrieval

However, raw IndexedDB is notoriously verbose. A simple read operation requires opening a connection, creating a transaction, obtaining an object store, and handling success/error events:

const request = indexedDB.open('MyDatabase', 1);
request.onsuccess = (event) => {
  const db = event.target.result;
  const tx = db.transaction('store', 'readonly');
  const store = tx.objectStore('store');
  const get = store.get('myKey');
  get.onsuccess = () => console.log(get.result);
};

This complexity makes IndexedDB error-prone and unpleasant for simple key-value use cases.


localForage: The Best of Both Worlds

localForage is a lightweight JavaScript library that wraps IndexedDB (with WebSQL and localStorage as fallbacks) behind a simple, asynchronous API inspired by localStorage. It automatically selects the best available storage engine.

npm install localforage

Basic Usage

import localforage from 'localforage';

// Store a value (supports objects, blobs, arrays)
await localforage.setItem('user', { name: 'Alice', role: 'admin' });

// Retrieve the value
const user = await localforage.getItem('user');
console.log(user.name); // "Alice"

// Remove a key
await localforage.removeItem('user');

// Clear all data
await localforage.clear();

// Get storage length
const len = await localforage.length();

The API is promise-based, so it works seamlessly with async/await or .then() chains.

Configuration

You can configure the driver priority, database name, and store name:

localforage.config({
  driver: [localforage.INDEXEDDB, localforage.LOCALSTORAGE],
  name: 'MyApp',
  storeName: 'app_cache',
  version: 1.0,
  description: 'Offline cache for MyApp'
});

Fallback Behavior

localForage detects the best available driver in this order:

  1. IndexedDB — preferred for its capacity and async model
  2. WebSQL — deprecated but still available in some browsers
  3. localStorage — last resort, with its 5 MB limitation

This ensures your application works everywhere while maximizing storage capacity when modern APIs are present.


Code Example: Caching API Responses

import localforage from 'localforage';

const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

async function fetchWithCache(url) {
  const cached = await localforage.getItem(url);
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }
  const response = await fetch(url);
  const data = await response.json();
  await localforage.setItem(url, { data, timestamp: Date.now() });
  return data;
}

This pattern reduces network requests while keeping the UI responsive — the async I/O never blocks rendering.


When to Use What

ScenarioRecommended Storage
Simple key-value, small data (< 5 MB)localStorage or localForage
Large JSON blobs, offline cacheslocalForage (IndexedDB backend)
Complex queries, indexes, large datasetsRaw IndexedDB
Cross-tab synchronizationIndexedDB + BroadcastChannel
Temporary session-only statesessionStorage

Conclusion

localStorage remains useful for truly small amounts of data, but its synchronous nature and 5 MB cap make it unsuitable for modern, data-intensive applications. Raw IndexedDB is powerful but overly complex for typical key-value scenarios. localForage bridges the gap by providing a clean, promise-based API that defaults to IndexedDB while gracefully degrading. For most client-side storage needs, localForage is the pragmatic choice.