Home / Articles / Practical notes: IndexedDB Isn’t Just Browser Storage: How to Build

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.

3095 words

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

Operational checklist