Modern web applications increasingly need to function offline or with unreliable network connections. localForage simplifies this challenge by providing a unified, promise-based API that abstracts away the complexity of browser storage options.
Building with LLMs often requires managing conversation history, cached embeddings, or temporary data across sessions. localForage provides a straightforward solution for persistent client-side storage that works seamlessly even when users go offline. The library wraps IndexedDB, WebSQL, and localStorage, automatically selecting the most capable option available in the user's browser, so you get generous storage capacity without the complexity of raw IndexedDB APIs according to the official localForage documentation.
For applications that need to store complex data types like embeddings, vectors, or conversation context, localForage offers a significant advantage over traditional browser storage solutions. Unlike localStorage, which only supports string values, localForage can natively store objects, arrays, dates, and even binary data without manual serialization.
If you're comparing storage options, our guide on localStorage examples covers the built-in browser storage API that localForage extends with modern capabilities.
Simple Promise-Based API
localForage presents a simple, promise-based API that mirrors the familiar localStorage interface while leveraging the power of modern browser storage capabilities.
Multiple Storage Backends
Automatically wraps IndexedDB, WebSQL, and localStorage, selecting the most capable option available in the user's browser.
Stores Complex Data Types
Unlike localStorage which only stores strings, localForage can store objects, arrays, dates, and binary data natively.
Asynchronous Operations
All operations are non-blocking, ensuring the main thread remains responsive during storage operations.
localStorage vs localForage: Understanding the Difference
localStorage, while simple, has significant limitations that become apparent as applications grow more complex:
localStorage Limitations
| Limitation | Impact |
|---|---|
| String-only storage | Requires manual JSON.stringify/parse for complex data |
| Synchronous blocking | Operations block the main thread, causing performance issues |
| Limited capacity | Typically only 5-10MB per origin |
| No transaction support | No built-in way to batch operations atomically |
How localForage Solves These Problems
localForage wraps multiple storage backends including IndexedDB, WebSQL, and localStorage, automatically selecting the most capable option available. This means you get:
- Unlimited storage capacity (browser-dependent, often 250MB+)
- Native object storage without serialization overhead
- Non-blocking operations using Promises
- Transaction support for complex operations
For applications that integrate with LLM APIs, the ability to cache responses locally can significantly reduce API costs and improve response times. The library was developed with support from Mozilla and has become a standard solution for offline storage in web applications as documented by LogRocket.
Getting Started with localForage
Installation
# npm installation
npm install localforage
# Import in your project
import localforage from 'localforage';
For browser-based projects without a build step:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/localforage.min.js"></script>
Initializing a Storage Instance
// Create a storage instance with custom configuration
const myStorage = localforage.createInstance({
name: 'myApplication',
storeName: 'userData',
description: 'Stores user preferences and session data'
});
The name parameter identifies your application, while storeName creates a distinct namespace within that storage. This separation becomes important when you need multiple independent storage instances, such as separating user preferences from application cache data as described in the configuration guide.
When building progressive web applications, proper storage instance configuration is essential for maintaining data isolation and optimal performance. The configuration also allows you to specify driver preferences if you need to ensure consistent behavior across different browsers.
Our comprehensive guide on web storage solutions provides additional context on browser storage patterns and when to use each approach.
Core API Methods
Storing Data with setItem
The setItem method saves data to storage, accepting a key and value pair. Unlike localStorage, localForage can store any serializable JavaScript value, including objects, arrays, dates, and even binary data like ArrayBuffers as explained in the LogRocket tutorial.
// Store simple values
await myStorage.setItem('username', 'john_doe');
// Store complex objects - no manual serialization needed
await myStorage.setItem('userProfile', {
id: 12345,
name: 'John Doe',
preferences: {
theme: 'dark',
language: 'en-US',
notifications: true
},
lastLogin: new Date()
});
Retrieving Data with getItem
// Retrieve stored values
const username = await myStorage.getItem('username');
// Retrieve complex objects
const profile = await myStorage.getItem('userProfile');
// Handle missing values gracefully
const settings = await myStorage.getItem('settings');
if (settings === null) {
console.log('No settings found, using defaults');
}
Removing and Clearing Data
// Remove a specific key
await myStorage.removeItem('temporaryToken');
// Clear all data from this storage instance
await myStorage.clear();
Iterating Over Stored Data
// Log all stored items
await myStorage.iterate((value, key, iterationNumber) => {
console.log(`[${iterationNumber}] ${key}:`, value);
});
// Stop iteration early by returning false
await myStorage.iterate((value, key) => {
if (value.id === targetId) {
return false; // Stop iteration
}
});
The ability to iterate efficiently over stored data makes localForage ideal for managing vector embeddings or cached model outputs that need to be searched or filtered. The iterate method provides a way to loop through all key-value pairs in storage, accepting a callback function that receives each value and key in sequence as documented in the API reference.
Storage Backends and Automatic Selection
One of localForage's most valuable features is its automatic backend selection. When you initialize a storage instance, localForage checks which storage mechanisms are available and selects the most capable option available. The priority order is: IndexedDB first, then WebSQL, with localStorage as a final fallback as explained in the storage backends documentation.
Priority Order
| Priority | Backend | Notes |
|---|---|---|
| 1 | IndexedDB | Best capacity and performance |
| 2 | WebSQL | Deprecated but still available in WebKit |
| 3 | localStorage | Universal support but string-only |
// Check which driver is currently in use
const driver = myStorage.driver;
console.log(`Using storage driver: ${driver}`);
// Explicit driver configuration
const storage = localforage.createInstance({
name: 'myApp',
storeName: 'data',
driver: [localforage.INDEXEDDB, localforage.LOCALSTORAGE]
});
When to Configure Drivers Explicitly
- When you want consistent behavior across browsers
- When WebSQL's deprecation status concerns you
- When you need specific storage characteristics
For production applications, relying on automatic selection ensures optimal performance while maintaining broad compatibility across different browsers and devices as recommended in the driver configuration guide.
Best Practices for Offline Storage
Data Serialization and Versioning
async function saveData(key, data, schemaVersion = 1) {
const wrappedData = {
schemaVersion,
timestamp: Date.now(),
payload: data
};
await myStorage.setItem(key, wrappedData);
}
async function loadData(key, currentVersion = 1) {
const wrappedData = await myStorage.getItem(key);
if (!wrappedData) return null;
if (wrappedData.schemaVersion < currentVersion) {
return migrateData(wrappedData, currentVersion);
}
return wrappedData.payload;
}
Managing Storage Capacity
// Estimate current storage usage
async function getStorageEstimate() {
if (navigator.storage && navigator.storage.estimate) {
const estimate = await navigator.storage.estimate();
console.log(`Usage: ${(estimate.usage / 1024 / 1024).toFixed(2)} MB`);
console.log(`Quota: ${(estimate.quota / 1024 / 1024).toFixed(2)} MB`);
return estimate;
}
}
// Implement cleanup for old entries
async function cleanupOldEntries(maxAge = 7 * 24 * 60 * 60 * 1000) {
const cutoff = Date.now() - maxAge;
const toRemove = [];
await myStorage.iterate((value, key) => {
if (value.timestamp && value.timestamp < cutoff) {
toRemove.push(key);
}
});
for (const key of toRemove) {
await myStorage.removeItem(key);
}
}
Error Handling
async function safeSetItem(key, value, maxRetries = 3) {
let attempts = 0;
while (attempts < maxRetries) {
try {
await myStorage.setItem(key, value);
return { success: true };
} catch (error) {
attempts++;
if (error.name === 'QuotaExceededError') {
await cleanupOldEntries();
}
if (attempts >= maxRetries) {
return { success: false, error };
}
await new Promise(r => setTimeout(r, Math.pow(2, attempts) * 100));
}
}
}
Implementing proper error handling and capacity management is critical for applications that cache LLM responses or embeddings, where stale data can impact model accuracy. Even with IndexedDB's generous limits, monitoring storage usage prevents unexpected quota exceeded errors as noted in the storage limits documentation.
Robust error handling ensures your application degrades gracefully when storage operations fail as recommended in the error handling guide.
Common Use Cases
Caching API Responses
One of the most common applications is caching API responses to reduce network traffic and improve responsiveness, especially important when building applications that work with LLMs and need to cache embedding vectors or model responses as described in offline-first patterns.
class ApiCache {
constructor(storage, ttl = 3600000) {
this.storage = storage;
this.ttl = ttl;
}
async get(key) {
const cached = await this.storage.getItem(key);
if (!cached) return null;
if (Date.now() - cached.timestamp > this.ttl) {
await this.storage.removeItem(key);
return null;
}
return cached.data;
}
async set(key, data) {
await this.storage.setItem(key, {
timestamp: Date.now(),
data
});
}
}
// Usage with LLM API responses
const apiCache = new ApiCache(myStorage);
async function fetchLLMResponse(prompt) {
const cacheKey = `llm:${hash(prompt)}`;
const cached = await apiCache.get(cacheKey);
if (cached) return cached;
const response = await callLLMApi(prompt);
await apiCache.set(cacheKey, response);
return response;
}
Offline-First Application State
class AppState {
constructor(storage) {
this.storage = storage;
this.state = {};
}
async load() {
const saved = await this.storage.getItem('appState');
if (saved) this.state = saved;
return this.state;
}
async save() {
await this.storage.setItem('appState', this.state);
}
set(key, value) {
this.state[key] = value;
this.save();
}
}
For applications that must function without network connectivity, localForage provides the foundation for persisting application state. This pattern ensures user changes are preserved even if the browser is closed before data syncs to a server as detailed in the offline-first development guide.
When storing multiple items, consider batching operations to reduce the overhead of individual storage calls as suggested in performance optimization tips.
Frequently Asked Questions
What browsers support localForage?
localForage supports all modern browsers. It automatically falls back from IndexedDB to WebSQL to localStorage depending on browser capabilities, ensuring universal compatibility.
How much data can localForage store?
With IndexedDB (the default backend), storage capacity depends on available disk space and browser limits, typically ranging from 250MB to several gigabytes. This is significantly more than localStorage's 5-10MB limit.
Is localForage synchronous or asynchronous?
All localForage operations are asynchronous and return Promises. This prevents blocking the main thread during storage operations, unlike localStorage which is synchronous.
Can localForage store binary data?
Yes, localForage can store ArrayBuffers and Blobs natively, making it suitable for caching images, audio, or other binary data directly in the browser.
Does localForage work offline?
Yes, localForage operates entirely client-side and doesn't require a network connection. Data persists locally even after the browser is closed and reopened.
Summary
localForage provides a powerful yet simple solution for persistent client-side storage. Its key advantages include:
- Simplicity: Promise-based API that's easy to learn and use
- Flexibility: Handles complex data types without manual serialization
- Performance: Asynchronous operations that don't block the main thread
- Reliability: Automatic fallback ensures compatibility across all browsers
- Capacity: Leverages IndexedDB for generous storage limits
For applications building with LLMs, localForage is particularly valuable for caching model responses, storing conversation history, and persisting embeddings locally--reducing API costs and improving user experience through offline functionality.
Next Steps
Ready to implement localForage in your project? Start with a simple storage instance and gradually incorporate more advanced features like caching strategies and error handling as your application grows. For teams building sophisticated AI-powered applications, proper offline storage is essential for delivering reliable user experiences.
Explore our web development services to learn how we can help you implement robust storage solutions, or discover our AI development capabilities for comprehensive LLM integration support.
Sources
- localForage - GitHub Repository - Official documentation and API reference
- LogRocket - localForage: Managing offline browser storage - Practical implementation guide
- DigitalOcean - How to Use localForage for Easy Async Browser Storage - Beginner-friendly tutorial