Add a Recordreadwrite tx
// Add inside a readwrite transaction
const tx = db.transaction('products', 'readwrite');
tx.objectStore('products').put({
id: Date.now(),
name, category, price
});
tx.oncomplete = () => refresh();
// transaction log appears here…
Live Object StoreShopDB v1
products0 records
No records yet. Add one or seed samples →
This is a real database. Open DevTools →
Application → IndexedDB → ShopDB to inspect the actual stored data. It survives a page refresh.
Query by Indexreadonly tx
// Query by index — only matching subset
const idx = tx.objectStore('products')
.index('by_category');
idx.getAll('tech').onsuccess = e =>
render(e.target.result);
// Range query with IDBKeyRange
const range = IDBKeyRange.bound(100, 500);
idx2.getAll(range);
// query results log here…
Matching Recordsno query yet
results—
Run a query to see matching records
Why indexes matter: without an index you can only fetch by primary key or scan every record. An index lets IndexedDB jump straight to matching records — fast, even with thousands of rows.
Cursor — One Record at a Timememory-efficient
cursor.value
— press Start —
// cursor steps log here…
How Cursors Work
// Cursor: onsuccess fires once per record
const req = store.openCursor();
req.onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) {
process(cursor.value); // one record
cursor.continue(); // next →
} else {
// cursor is null = done
}
};
Cursor vs getAll():
getAll() loads every record into memory at once. A cursor holds just one record at a time — essential for large datasets. Each continue() advances one step. When the cursor is null, you've reached the end.
Direction:
openCursor(range, 'prev') walks descending. 'nextunique' skips duplicate keys. Cursors over an index walk only the matching subset, in index order.