45. IndexedDB: transactional storage for structured, larger data
IndexedDB is a full asynchronous, transactional, indexed database built into the browser — the right tool once data outgrows Web Storage's 5-10MB string-only model (offline app data, cached API responses, encrypted blobs from §46-48).
function openDB() {
return new Promise((resolve, reject) => {
const req = indexedDB.open("notes-app", 1);
req.onupgradeneeded = (e) => {
const db = e.target.result;
const store = db.createObjectStore("notes", { keyPath: "id" });
store.createIndex("by-date", "createdAt"); // queryable secondary index
};
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
async function addNote(db, note) {
return new Promise((resolve, reject) => {
const tx = db.transaction("notes", "readwrite");
tx.objectStore("notes").add(note);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
async function getAllNotesSortedByDate(db) {
return new Promise((resolve, reject) => {
const tx = db.transaction("notes", "readonly");
const index = tx.objectStore("notes").index("by-date");
const results = [];
index.openCursor().onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) { results.push(cursor.value); cursor.continue(); }
else resolve(results);
};
tx.onerror = () => reject(tx.error);
});
}
The raw callback-based API is notoriously verbose — in real projects, wrap
it with a promise-based library (idb is the standard choice) rather than
hand-rolling this every time; the example above shows what that library is
doing underneath so debugging a leaked transaction or a version-upgrade bug
isn't a black box.