Using IndexedDB Cursors and Key Ranges in JavaScript

This article provides an overview of IndexedDB cursors and explains how to iterate through records using filtered key ranges in JavaScript. You will learn the fundamental mechanisms of IDBCursor and IDBKeyRange, how to construct precise boundary conditions, and how to write efficient traversal logic to query client-side databases without loading entire datasets into memory.

Understanding IndexedDB Cursors

An IndexedDB cursor (IDBCursor) is a pointer that enables sequential traversal over multiple records in an object store or index. Instead of fetching all matching records into memory at once with methods like getAll(), a cursor loads records individually on demand. This makes cursors memory-efficient and ideal for working with large datasets, generating reports, or modifying records during traversal.

When iterating with cursors, you typically use IDBCursorWithValue, which gives access to both the primary key (cursor.key), the primary index key (cursor.primaryKey), and the stored object itself (cursor.value).

Defining Filtered Key Ranges with IDBKeyRange

The IDBKeyRange interface defines criteria to limit which records a cursor visits. Instead of scanning the entire store, a key range restricts traversal to keys that satisfy specific boundary conditions.

The IDBKeyRange utility provides four static factory methods:

// Matches ages between 21 and 65, inclusive
const standardRange = IDBKeyRange.bound(21, 65);

// Matches ages strictly greater than 21 and strictly less than 65 (exclusive)
const exclusiveRange = IDBKeyRange.bound(21, 65, true, true);

// Matches all prices 100 or greater
const minimumPrice = IDBKeyRange.lowerBound(100);

How JavaScript Iterates Through a Key Range

To iterate through records using a cursor and key range, follow these steps:

  1. Start a Transaction: Create a readonly or readwrite transaction on the target object store.
  2. Access the Store or Index: Obtain a reference to the object store or an index configured on the field you want to filter.
  3. Open the Cursor: Call openCursor(keyRange, direction) on the store or index.
  4. Handle the onsuccess Event: Process the current record via event.target.result.
  5. Advance the Cursor: Call cursor.continue() to move to the next matching item.

Implementation Example

function iterateFilteredRecords(db) {
  const transaction = db.transaction(['users'], 'readonly');
  const store = transaction.objectStore('users');
  const index = store.index('ageIndex');

  // Define a range: ages 18 to 30 inclusive
  const ageRange = IDBKeyRange.bound(18, 30);

  // Open cursor passing the range and optional direction ('next', 'prev', 'nextunique', 'prevunique')
  const request = index.openCursor(ageRange, 'next');

  request.onsuccess = function (event) {
    const cursor = event.target.result;

    if (cursor) {
      // Access the current matching record
      console.log(`Key: ${cursor.key}, Value:`, cursor.value);

      // Advance to the next matching record
      cursor.continue();
    } else {
      // Reached the end of the matching range
      console.log('Iteration complete.');
    }
  };

  request.onerror = function (event) {
    console.error('Cursor iteration error:', event.target.error);
  };
}

Controlling Iteration Flow

Cursors provide additional methods to control how the iteration progresses:

By pairing IDBCursor with IDBKeyRange, JavaScript applications can query and manipulate indexed client-side data with precision, minimal memory overhead, and full directional control.