This article is published in English.
Practical notes: IndexedDB Isn’t Just Browser Storage: How to Build
Operable walkthrough of Practical notes: IndexedDB Isn’t Just Browser Storage: How to Build: contracts, checks, and drop-in code slots for teams shipping this pattern.
This walkthrough rebuilds the path from raw materials to a working system for: IndexedDB Isn’t Just Browser Storage: How to Build Offline-First Web Apps. The focus is operable steps, explicit checks, and code that you can drop into a repo without guessing intent. For the Overview stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
localStorage Stops Being Enough
When working through the localStorage Stops Being Enough stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Measure recall on a fixed question set before tuning prompts. Prompt churn rarely fixes a weak retrieval surface.
localStorage.setItem("theme", "dark");
const theme = localStorage.getItem("theme");
localStorage.setItem(
"user",
JSON.stringify(user)
);
const user = JSON.parse(
localStorage.getItem("user")
);
50,000 products
10,000 orders
Thousands of customer records
Offline mutations
Synchronization metadata
IndexedDB Is a Database Inside the Browser
When working through the IndexedDB Is a Database stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Measure recall on a fixed question set before tuning prompts. Prompt churn rarely fixes a weak retrieval surface.
Web Application
│
▼
IndexedDB
│
├── Users
├── Products
├── Orders
├── Messages
└── Pending Sync
The Core Concepts
When working through the The Core Concepts stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Measure recall on a fixed question set before tuning prompts. Prompt churn rarely fixes a weak retrieval surface. When working through the The Core Concepts stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
Database
The Database stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Separate chunking policy from retrieval policy. Changing one should not force a rewrite of the other when quality metrics move.
my-app-db
Object Store
The Object Store stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Separate chunking policy from retrieval policy. Changing one should not force a rewrite of the other when quality metrics move.
users
orders
products
Key
The Key stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Separate chunking policy from retrieval policy. Changing one should not force a rewrite of the other when quality metrics move. The Key stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
user_123
order_456
Index
For the Index stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Cite the passages that actually grounded the answer. Without citations, operators cannot tell hallucination from an indexing gap.
orders
├── id
├── customerId
├── status
└── createdAt
customerId
Transaction
For the Transaction stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Cite the passages that actually grounded the answer. Without citations, operators cannot tell hallucination from an indexing gap.
A Simple Example
For the A Simple Example stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Cite the passages that actually grounded the answer. Without citations, operators cannot tell hallucination from an indexing gap. For the A Simple Example stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
const request = indexedDB.open("MyAppDB", 1);
request.onupgradeneeded = () => {
const db = request.result; db.createObjectStore("users", {
keyPath: "id"
});
};request.onsuccess = () => {
const db = request.result; console.log("Database opened");
};
const transaction = db.transaction(
"users",
"readwrite"
);
const store = transaction.objectStore("users");store.put({
id: "user_123",
name: "Alex",
email: "alex@example.com"
});
const transaction = db.transaction(
"users",
"readonly"
);
const store = transaction.objectStore("users");const request = store.get("user_123");request.onsuccess = () => {
console.log(request.result);
};
Why Indexes Matter
When working through the Why Indexes Matter stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Measure recall on a fixed question set before tuning prompts. Prompt churn rarely fixes a weak retrieval surface.
100,000 orders
All orders for customer_123
orders
│
├── Primary Key: id
│
└── Index: customerId
customerId = customer_123
│
▼
Index
│
▼
matching orders
Transactions Are More Important Than They Look
When working through the Transactions Are More Important stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Measure recall on a fixed question set before tuning prompts. Prompt churn rarely fixes a weak retrieval surface.
Transaction
│
├── Create Order
├── Create Order Items
└── Create Sync Job
│
▼
COMMIT
ROLLBACK
IndexedDB Enables Offline-First Architecture
When working through the IndexedDB Enables Offline-First Architecture stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Measure recall on a fixed question set before tuning prompts. Prompt churn rarely fixes a weak retrieval surface. When working through the IndexedDB Enables Offline-First Architecture stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
No Internet
↓
Application stops working
┌──────────────┐
│ Web App │
└──────┬───────┘
│
┌──────▼───────┐
│ IndexedDB │
└──────┬───────┘
│
┌──────▼───────┐
│ Sync Engine │
└──────┬───────┘
│
Internet?
/ \
No Yes
│ │
▼ ▼
Stay API
Local │
▼
Server
The Outbox Pattern in the Browser
The The Outbox Pattern in stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Separate chunking policy from retrieval policy. Changing one should not force a rewrite of the other when quality metrics move.
orders
outbox
{
id: "event_123",
type: "ORDER_CREATED",
aggregateId: "order_456",
payload: {...},
status: "pending",
createdAt: 1724930000
}
IndexedDB
│
▼
Pending Outbox Events
│
▼
Sync Worker
│
▼
API
│
▼
Server
pending
↓
synced
But Offline Sync Creates New Problems
The But Offline Sync Creates stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Separate chunking policy from retrieval policy. Changing one should not force a rewrite of the other when quality metrics move.
Name = "John"
Name = "Jonathan"
Last Write Wins
The Last Write Wins stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Separate chunking policy from retrieval policy. Changing one should not force a rewrite of the other when quality metrics move. The Last Write Wins stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
Server Wins
For the Server Wins stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Cite the passages that actually grounded the answer. Without citations, operators cannot tell hallucination from an indexing gap.
Client Wins
For the Client Wins stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Cite the passages that actually grounded the answer. Without citations, operators cannot tell hallucination from an indexing gap.
Field-Level Merge
For the Field-Level Merge stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Cite the passages that actually grounded the answer. Without citations, operators cannot tell hallucination from an indexing gap. For the Field-Level Merge stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
Conflict Resolution
When working through the Conflict Resolution stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Measure recall on a fixed question set before tuning prompts. Prompt churn rarely fixes a weak retrieval surface.
Idempotency Matters Here Too
When working through the Idempotency Matters Here Too stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Measure recall on a fixed question set before tuning prompts. Prompt churn rarely fixes a weak retrieval surface.
ORDER_CREATED
Request 1 → SUCCESS
Request 2 → SUCCESS
clientMutationId = "mutation_123"
UNIQUE(clientMutationId)
Don’t Treat IndexedDB as Your Server Database
When working through the Don t Treat IndexedDB stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Measure recall on a fixed question set before tuning prompts. Prompt churn rarely fixes a weak retrieval surface. When working through the Don t Treat IndexedDB stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
Server
│
PostgreSQL
│
▼
API
▲
│
Sync Layer
▲
│
IndexedDB
▲
│
Web App
What Should You Store?
The What Should You Store stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Separate chunking policy from retrieval policy. Changing one should not force a rewrite of the other when quality metrics move.
Products
Recent Orders
Customer Data
Drafts
User Preferences
Offline Mutations
Sync Metadata
Storage Isn’t Unlimited
The Storage Isn t Unlimited stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Separate chunking policy from retrieval policy. Changing one should not force a rewrite of the other when quality metrics move.
IndexedDB Is Not a Cache by Default
The IndexedDB Is Not a stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Separate chunking policy from retrieval policy. Changing one should not force a rewrite of the other when quality metrics move. The IndexedDB Is Not a stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
A Practical Architecture
For the A Practical Architecture stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Cite the passages that actually grounded the answer. Without citations, operators cannot tell hallucination from an indexing gap.
┌───────────────┐
│ Web Client │
└───────┬───────┘
│
┌──────────▼──────────┐
│ Application State │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ IndexedDB │
│ │
│ Products │
│ Orders │
│ Drafts │
│ Outbox │
│ Sync Metadata │
└──────────┬──────────┘
│
Sync Engine
│
┌────────▼────────┐
│ API │
└────────┬────────┘
│
┌────────▼────────┐
│ Database │
└─────────────────┘
Common Mistakes
For the Common Mistakes stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Mistake 1: Using localStorage for everything
Mistake 2: Treating IndexedDB as a permanent source of truth
Mistake 3: Ignoring conflicts
Mistake 4: Forgetting duplicate synchronization
Mistake 5: Storing everything
Mistake 6: Designing offline support at the end
The Bigger Lesson
Final Thought
Browser
↕
Server