LEARNING OBJECTIVES ⌵
- Understand how
IDBIndexenables fast lookups on non-primary object properties. - Create unique, compound, and
multiEntryarray indexes usingcreateIndex(). - Construct targeted key queries with
IDBKeyRange.only(),bound(),lowerBound(), andupperBound(). - Execute range queries across secondary indexes using
index.getAll()andindex.count().
📖 The Mental Model & Story (Intuitive Foundation)
Imagine a traditional university library with 100,000 physical books.
The books are arranged on shelves strictly by their call number / barcode (the Primary Key). If you know the exact barcode, you can walk straight to the shelf and grab the book in seconds.
What happens if you don't know the barcode, but you want to find:
- "All books authored by Stephen King?"
- "All computer science textbooks published between 2020 and 2024?"
- "All books tagged with the keyword 'algorithms'?"
Without an Index, the librarian would have to inspect every single one of the 100,000 books from shelf 1 to shelf 1,000 (an $O(N)$ full table scan).
An IDBIndex is the physical card catalog system in the center of the library:
- The Author Catalog (
by_author): An alphabetized card index pointing directly to the shelf location of every book. - The Publication Date Range (
IDBKeyRange.bound(2020, 2024)): Pulling out only the drawer slice between 2020 and 2024. - The Multi-Entry Subject Tag Index (
multiEntry: true): If a book has three tags (['AI', 'Robotics', 'Python']), the librarian files three separate index cards so the book can be discovered through any of those three search terms.
Technical Deep Dive & Specifications
The createIndex() Method
Indexes can only be declared inside onupgradeneeded via the IDBObjectStore instance:
store.createIndex(indexName, keyPath, {
unique: false, // Enforces strict uniqueness if true
multiEntry: false // If keyPath resolves to an Array, indexes each element individually
});
The 4 Index Types
+----------------------------------------------------------------------------------------------------+
| INDEXEDDB INDEX TAXONOMY |
+-------------------+----------------------------+-----------------+---------------------------------+
| Index Type | Definition | Stored Data | Resulting Index Keys |
+-------------------+----------------------------+-----------------+---------------------------------+
| 1. Standard Index | `createIndex('by_email', | `{ email: | Key: `"[email protected]"` ➔ |
| | 'email', {unique:true})` | "alice..." }`| Points to Record Primary Key |
+-------------------+----------------------------+-----------------+---------------------------------+
| 2. Multi-Entry | `createIndex('by_tag', | `{ tags: | Key: `"web"` ➔ Points to PKey |
| (Array Index) | 'tags', {multiEntry:t})` | ['web','js']}`| Key: `"js"` ➔ Points to PKey |
+-------------------+----------------------------+-----------------+---------------------------------+
| 3. Compound Index | `createIndex('dept_salary',| `{ dept: "eng", | Key: `["eng", 120000]` ➔ |
| (Multi-Field) | ['dept', 'salary'])` | salary: ...}`| Points to Record Primary Key |
+-------------------+----------------------------+-----------------+---------------------------------+
| 4. Nested Path | `createIndex('by_city', | `{ address: { | Key: `"Austin"` ➔ |
| (Dot Notation) | 'address.city')` | city: ...}}` | Points to Record Primary Key |
+-------------------+----------------------------+-----------------+---------------------------------+
IDBKeyRange Range Mechanics
To query an index or store within a specific boundary, use the global IDBKeyRange factory:
IDBKeyRange Methods & Intervals
1. IDBKeyRange.only(10)
Matches ONLY: [ 10 ]
2. IDBKeyRange.lowerBound(10, false) --> [10, +Infinity) (Inclusive: >= 10)
IDBKeyRange.lowerBound(10, true) --> (10, +Infinity) (Exclusive: > 10)
3. IDBKeyRange.upperBound(50, false) --> (-Infinity, 50] (Inclusive: <= 50)
IDBKeyRange.upperBound(50, true) --> (-Infinity, 50) (Exclusive: < 50)
4. IDBKeyRange.bound(10, 50, false, false) --> [10, 50] (10 <= x <= 50)
IDBKeyRange.bound(10, 50, true, false) --> (10, 50] (10 < x <= 50)
IDBKeyRange.bound(10, 50, false, true) --> [10, 50) (10 <= x < 50)
IDBKeyRange.bound(10, 50, true, true) --> (10, 50) (10 < x < 50)
💻 Interactive Code Playground
Starter Code
Save this file as indexes.html and open it in your browser.
Line-by-Line Code Breakdown
- Line 58 (
store.createIndex('by_price', 'price', { unique: false })): Generates an index over the numericpriceattribute allowing multiple items to share identical prices. - Line 60 (
store.createIndex('by_tag', 'tags', { multiEntry: true })): Unpacks thetags: ['portable', 'pro', 'wireless']array into three distinct index entries pointing to the same record. - Line 62 (
store.createIndex('by_cat_price', ['category', 'price'])): Creates a compound B-tree index sorted primary-by-category, secondary-by-price. - Lines 84–92 (
IDBKeyRange.bound(100, 500, false, false)): Defines a closed interval $[100, 500]$ and queries matching records viaindex.getAll(priceRange). - Lines 108–117 (
IDBKeyRange.bound(['electronics', 400], ['electronics', 3000])): Filters the compound index exclusively for the'electronics'category with prices ranging from $400 to $3,000.
Expected Browser Render Output
[02:55:00] 🛠️ CatalogDB configured with 3 indexes and 5 seeded records.
[02:55:00] ✅ CatalogDB ready for queries.
[02:55:05] 📊 Found 3 items between $100 and $500:
- Mechanical Keyboard ($120)
- Noise-Canceling Headphones ($350)
- 4K Monitor ($450)
[02:55:10] 🏷️ Found 3 items with tag "wireless":
- Wireless Mouse (Tags: wireless, usb)
- Noise-Canceling Headphones (Tags: audio, wireless)
- Laptop Pro 16 (Tags: portable, pro, wireless)🏋️ Hands-On Exercise
🎯 The Challenge: Filter Employees by Department & Salary Threshold
Instructions:
- Open database
HRSystemwith storeemployees(keyPath: "id"). - Create an index
by_salaryon propertysalary. - Seed 4 employees:
{ id: 'e1', name: 'John Doe', department: 'Engineering', salary: 110000 }{ id: 'e2', name: 'Jane Smith', department: 'Sales', salary: 95000 }{ id: 'e3', name: 'Alice Lee', department: 'Engineering', salary: 145000 }{ id: 'e4', name: 'Bob Ray', department: 'Marketing', salary: 70000 }
- Query all employees earning strictly greater than $100,000 using
IDBKeyRange.lowerBound(100000, true)(exclusive lower bound) and display their names.
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Forgetting
multiEntry: trueon Array Properties: If an object hastags: ['a', 'b']andmultiEntryisfalse(the default), IndexedDB treats the whole['a', 'b']array as a single compound key. Querying for the individual string'a'will yield zero results. - Attempting to Create Indexes Outside
onupgradeneeded: Callingstore.createIndex()inside a regular transaction throws anInvalidStateError. - Unique Index Violations Rolling Back Transactions: If an index is marked
{ unique: true }, anystore.add()orstore.put()that introduces a duplicate index value will throwConstraintErrorand abort the parent transaction.
💡 Pro Tips
- Order Matters in Compound Indexes: An index on
['country', 'state', 'city']can optimize queries on['country']or['country', 'state'], but cannot optimize queries filtering solely by'city'. Define your index key paths from highest to lowest cardinality. - Use
index.count(range)for Pagination Metadata: To display total results without fetching payloads, queryindex.count(range).
📌 Key Takeaways
- An
IDBIndexcreates a secondary B-Tree search index on one or more non-primary properties. multiEntry: truesplits array properties so that every individual element becomes an index key.IDBKeyRangeprovides four interval constructors:only(),lowerBound(),upperBound(), andbound().- The boolean parameters in
bound()control whether interval bounds are open (exclusive) or closed (inclusive). - Indexes are queried using
index.get(),index.getAll(),index.getKey(), andindex.count(). - --