Chapter 49: IndexedDB Client-Side Database

CRUD Operations

Mastering data mutations: inserting with `add()`, querying with `get()` & `getAll()`, upserting with `put()`, removing with `delete()`, and managing `IDBRequest` events.

LEARNING OBJECTIVES
  • Differentiate between add() (strict insert) and put() (upsert/replace).
  • Query records accurately using get(), getAll(), count(), and getKey().
  • Remove records safely using delete() and purge collections with clear().
  • Understand the IDBRequest lifecycle, event bubbling hierarchy, and error propagation.
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 The Mental Model & Story (Intuitive Foundation)

Imagine a physical post office box system with numbered lockboxes.

  1. add(record) (Strict New Lease): You attempt to place a package into Box #42. If Box #42 already contains a package, the clerk firmly stops you: "Halt! Box #42 is occupied!" (A ConstraintError is raised).
  2. put(record) (Upsert / Overwrite): You tell the clerk: "Deliver this package to Box #42. If it's empty, put it in. If there's already something in there, replace it completely."
  3. get(key) (Fetch): You ask the clerk to look inside Box #42. If it contains an item, you receive it. If it is empty, you are handed undefined (not an error—just an empty box).
  4. delete(key) (Remove): You instruct the clerk to empty Box #42. If Box #42 was already empty, the operation still succeeds silently without error.
  5. The IDBRequest Ticket: Every time you make a request at the counter, the clerk hands you a claim ticket (IDBRequest). You cannot peek at the contents immediately. When the clerk returns from the back room, your claim ticket lights up with onsuccess and you can read request.result.

Technical Deep Dive & Specifications

CRUD Methods on IDBObjectStore

+----------------------------------------------------------------------------------------------------+
|                                    IDBOBJECTSTORE CRUD API MATRIX                                  |
+-------------+-----------------------------+------------------------------------+-------------------+
| Method      | Signature                   | Behavior                           | Return Value      |
+-------------+-----------------------------+------------------------------------+-------------------+
| `add()`     | `add(value, [key])`         | Inserts record. Fails if key exists| Key of new record |
+-------------+-----------------------------+------------------------------------+-------------------+
| `put()`     | `put(value, [key])`         | Inserts or replaces existing record| Key of record     |
+-------------+-----------------------------+------------------------------------+-------------------+
| `get()`     | `get(key)`                  | Fetches record by primary key      | Object or undef   |
+-------------+-----------------------------+------------------------------------+-------------------+
| `getKey()`  | `getKey(query)`             | Fetches only the primary key       | Key value         |
+-------------+-----------------------------+------------------------------------+-------------------+
| `getAll()`  | `getAll([query], [count])`  | Fetches array of matching records  | Array of objects  |
+-------------+-----------------------------+------------------------------------+-------------------+
| `delete()`  | `delete(key)`               | Removes record by primary key      | `undefined`       |
+-------------+-----------------------------+------------------------------------+-------------------+
| `clear()`   | `clear()`                   | Deletes ALL records in the store   | `undefined`       |
+-------------+-----------------------------+------------------------------------+-------------------+
| `count()`   | `count([query])`            | Counts number of matching records  | Integer count     |
+-------------+-----------------------------+------------------------------------+-------------------+

The IDBRequest Event & Bubbling Lifecycle

Whenever a CRUD operation is invoked, it synchronously returns an IDBRequest object in the "pending" state. When the underlying disk I/O completes, the browser fires events that bubble up through three tiers:

                      +-----------------------------+
                      |   IDBRequest (Operation)    |
                      |   [onsuccess / onerror]     |
                      +-----------------------------+
                                     |
                                     v (Bubbles on error)
                      +-----------------------------+
                      |   IDBTransaction (Scope)    |
                      |   [oncomplete / onerror]    |
                      +-----------------------------+
                                     |
                                     v (Bubbles on error)
                      +-----------------------------+
                      |    IDBDatabase (Engine)     |
                      |   [onerror]                 |
                      +-----------------------------+

Crucial Rule: onsuccess events do not bubble. You must attach onsuccess directly to the IDBRequest. However, onerror events do bubble up to the parent IDBTransaction and IDBDatabase unless event.stopPropagation() or event.preventDefault() is called.


💻 Interactive Code Playground

Starter Code

Save this file as crud.html and open it in your browser.

Line-by-Line Code Breakdown

  • Line 72 (store.add(data)): Executes an insertion. If a record with id: "u101" already exists, this triggers request.onerror with a ConstraintError.
  • Line 87 (store.put(data)): Executes an upsert. If id: "u101" exists, it is cleanly overwritten with the new object; if it does not exist, it is inserted.
  • Lines 100–108 (store.get(id)): Fetches the record. Notice that a missing record is not an error; request.result is simply undefined.
  • Line 115 (store.getAll()): Fetches an array containing every record currently stored in the ObjectStore.
  • Line 129 (store.delete(id)): Deletes the specified key. Even if the key does not exist, delete() succeeds silently.
  • Line 139 (store.clear()): Truncates the entire ObjectStore, removing all records while preserving the store itself and its index definitions.

Expected Browser Render Output


SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...
[02:45:00] 🛠️ Created ObjectStore: "users" with keyPath "id"
[02:45:00] ✅ Database connected and ready.
[02:45:05] ➕ ADD Success: Inserted key "u101"
[02:45:10] ❌ ADD Failed: ConstraintError - Key already exists in the object store.
[02:45:15] 🔄 PUT Success: Upserted key "u101"
[02:45:20] 🔍 GET Result: {"id":"u101","name":"Sarah Connor","role":"Admin","updatedAt":"2026-08-21T02:45:15.100Z"}

🏋️ Hands-On Exercise

🎯 The Challenge: Build a Resilient Shopping Cart Store

Instructions:

  1. Open database ECommerceApp at Version 1 with store cart_items (keyPath: "sku").
  2. Create an addToCart(item) function that checks if an item already exists:
    • If it does not exist, insert it with quantity: 1.
    • If it already exists, increment its quantity by 1 and update it using put().
  3. Test by adding an item { sku: "AIR-JORDAN-1", name: "Nike Air Jordan 1", price: 180 } three times and verify the final quantity is 3.

🏁 Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

⚠️ Common Pitfalls

  1. Expecting get() on a Missing Key to Trigger onerror: If a key does not exist, store.get("missing_key") succeeds normally and sets request.result to undefined. Always test if (req.result !== undefined) in onsuccess.
  2. Using add() when put() is Intended: Using add() in forms or synchronization routines will cause unexpected ConstraintError exceptions when records are re-submitted.
  3. Uncontrolled getAll() Memory Spikes: Calling store.getAll() on an ObjectStore containing tens of thousands of large objects loads the entire dataset into JavaScript heap memory at once, potentially causing tab crashes. Use Cursors (Lesson 49.7) for large datasets.

💡 Pro Tips

  1. Use count() for Existence Checks: If you only need to know whether a record exists or count matching items, call store.count(key). This checks the index B-tree without deserializing the underlying object payload into JavaScript memory.
  2. Leverage getKey() to Avoid Payload Cloning: If you only need the primary key of a query match, store.getKey(query) avoids the Structured Clone overhead of the entire value object.

📌 Key Takeaways

  • add() is an insert-only operation that fails with ConstraintError if the primary key already exists.
  • put() is an upsert operation that inserts new records or overwrites existing ones.
  • get() returns the matching record or undefined (it does not fail if the key is missing).
  • delete() removes a record by primary key silently, even if the key is not present.
  • getAll() retrieves an array of all records; use with caution on large datasets.
  • onsuccess events do not bubble, while onerror events bubble to the parent IDBTransaction and IDBDatabase.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

What is the value of request.result when store.get('non_existent_key') finishes?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

What happens if you execute store.add(record) with a primary key that already exists in the ObjectStore?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

Which method should you call if you need to know how many records exist without loading their full object contents into memory?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP