IndexedDB is a full NoSQL database in the browser. At Google, we use it for offline-first applications and caching large datasets.
Why IndexedDB?
| Storage | Capacity | Sync/Async | Data Type |
|---|---|---|---|
| localStorage | 5MB | Synchronous | Strings only |
| sessionStorage | 5MB | Synchronous | Strings only |
| IndexedDB | GBs | Asynchronous | Any structured data |
Basic Operations with idb Library
import { openDB } from "idb";
// Create/upgrade database
const db = await openDB("MyApp", 1, {
upgrade(db) {
const store = db.createObjectStore("articles", { keyPath: "id" });
store.createIndex("by-date", "publishedAt");
store.createIndex("by-category", "categoryId");
}
});
// Create
await db.put("articles", {
id: "article-1",
title: "Hello World",
publishedAt: new Date(),
categoryId: "tech"
});
// Read
const article = await db.get("articles", "article-1");
// Read all
const allArticles = await db.getAll("articles");
// Query by index
const techArticles = await db.getAllFromIndex("articles", "by-category", "tech");
// Delete
await db.delete("articles", "article-1");Transactions
const tx = db.transaction("articles", "readwrite");
const store = tx.objectStore("articles");
await Promise.all([
store.put({ id: "1", title: "First" }),
store.put({ id: "2", title: "Second" }),
tx.done // Wait for transaction to complete
]);Offline-First Pattern
async function getArticles() {
// Try network first
try {
const response = await fetch("/api/articles");
const articles = await response.json();
// Cache in IndexedDB
const tx = db.transaction("articles", "readwrite");
articles.forEach(a => tx.objectStore("articles").put(a));
await tx.done;
return articles;
} catch {
// Fall back to cached data
return db.getAll("articles");
}
}Best Practices
- Always use a wrapper library like
idb— the raw API is callback-based and painful - Version your schema and handle upgrades carefully
- Use indexes for any field you query by
- Keep transactions short — they auto-commit when idle
- Handle QuotaExceededError gracefully