Skip to content

IndexedDB - Cursors

While methods like get() and getAll() are great for retrieving specific records or a full dataset, they can be inefficient if the dataset is very large, as getAll() loads every record into memory. Cursors provide a powerful, memory-efficient way to iterate through records in an object store or index one by one.

A cursor is like a pointer that moves through your records. You open a cursor, process the record at the cursor’s current position, and then tell it to advance to the next one.

Modern Alternatives: getAll() and getAllKeys()

Section titled “Modern Alternatives: getAll() and getAllKeys()”

Before diving into cursors, always consider if a simpler method fits your needs. For moderately sized datasets, getAll() is often sufficient and leads to much simpler code.

// Get all objects from the store
const allRecords = await store.getAll();
// Get all keys from the store
const allKeys = await store.getAllKeys();

Use cursors when you need to:

  • Iterate over a very large number of records without high memory usage.
  • Process, update, or delete records one by one based on a certain logic.
  • Implement complex queries that cannot be achieved with getAll() and a key range alone.

You create a cursor using the openCursor() method on an object store or an index. The method is asynchronous and returns an IDBRequest object.

objectStore.openCursor(range, direction);
  • range (optional): An IDBKeyRange to limit the iteration to specific keys. If null, it iterates over the entire store.
  • direction (optional): A string specifying the iteration direction. Can be 'next' (default), 'prev', 'nextunique', or `‘prevunique’.

Here is a complete HTML example showing how to use a cursor to read all records from an object store.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>IndexedDB Cursors</title>
</head>
<body>
<h1>Reading data with a cursor. Check the console.</h1>
<ul id="user-list"></ul>
<script>
const DB_NAME = "UserDB";
const STORE_NAME = "users";
const userList = document.getElementById('user-list');
// --- Database Setup (omitted for brevity, assume it's set up) ---
async function setupAndPopulate() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, 1);
request.onupgradeneeded = e => {
const db = e.target.result;
const store = db.createObjectStore(STORE_NAME, { keyPath: 'id' });
store.createIndex('by_name', 'name', { unique: false });
// Populate with sample data
store.put({ id: 1, name: 'Alice', role: 'Admin' });
store.put({ id: 2, name: 'Bob', role: 'Editor' });
store.put({ id: 3, name: 'Charlie', role: 'Viewer' });
store.put({ id: 4, name: 'Diana', role: 'Editor' });
};
request.onsuccess = e => resolve(e.target.result);
request.onerror = e => reject(e.target.error);
});
}
async function iterateWithCursor(db) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, 'readonly');
const store = transaction.objectStore(STORE_NAME);
const request = store.openCursor();
request.onsuccess = event => {
const cursor = event.target.result;
if (cursor) {
// We have a record
const user = cursor.value;
console.log(`Key: ${cursor.key}, Value:`, user);
// Display it in the UI
const li = document.createElement('li');
li.textContent = `ID: ${user.id}, Name: ${user.name}, Role: ${user.role}`;
userList.appendChild(li);
// Move to the next record
cursor.continue();
} else {
// No more records
console.log('Cursor finished.');
resolve();
}
};
request.onerror = event => {
reject(event.target.error);
};
});
}
async function main() {
// Clear previous DB for a clean run
indexedDB.deleteDatabase(DB_NAME).onsuccess = async () => {
const db = await setupAndPopulate();
await iterateWithCursor(db);
db.close();
}
}
main();
</script>
</body>
</html>
  • Updating Records: Inside a 'readwrite' transaction, you can call cursor.update(newValue) to modify the record at the cursor’s current position.
  • Deleting Records: Similarly, in a 'readwrite' transaction, cursor.delete() will remove the current record.

For example, to update all ‘Editor’ roles to ‘Contributor’:

const request = store.openCursor();
request.onsuccess = event => {
const cursor = event.target.result;
if (cursor) {
if (cursor.value.role === 'Editor') {
const updatedUser = { ...cursor.value, role: 'Contributor' };
cursor.update(updatedUser);
}
cursor.continue();
}
};