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 → ApplicationIndexedDBShopDB 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.
Read the tutorial