IndexedDB: The Local Database That Makes Browser Apps Stateful

 

IndexedDB: The Local Database That Makes Browser Apps Stateful

How the browser stores structured data locally so your app can remember everything

#JavaScript #HTML #WebAPIs #IndexedDB




IndexedDB is the Browser's Real Database

Most developers know IndexedDB exists. Very few understand what it actually enables.

IndexedDB is the browser's real database — a transactional, asynchronous, persistent key-value store that lets your application remember things across sessions, even offline, even without a backend. If the File System Access API gives your dashboard access to files, IndexedDB gives it access to state. This is the second foundational pillar of a local-first architecture.


What IndexedDB Actually Is

IndexedDB is a NoSQL object database built into every modern browser. It's fully asynchronous, transactional (ACID), persistent across sessions, and designed for structured data — not just strings. It can store large objects, including file handles, and it's the only place you can safely persist FileSystemHandle objects for permission recovery. It's not "localStorage but bigger." It's closer to a lightweight, browser-native MongoDB. (One note: the native API is callback-based and can be awkward — you'll almost always wrap it in Promises, as shown below, or use a tiny library like idb.)


The Core Mental Model

IndexedDB revolves around three concepts: a database (a named, versioned container), object stores (like tables, but schemaless), and transactions (atomic read/write operations). Once you understand these three, the entire API becomes predictable.


Opening a Database (The First Step)

IndexedDB uses a versioned upgrade model. Here's the Promise-wrapped pattern you'll use every time:

const db = await new Promise((resolve, reject) => {
  const request = indexedDB.open("dashboard-db", 1);

  request.onupgradeneeded = event => {
    const db = event.target.result;
    db.createObjectStore("settings", { keyPath: "id" });
  };

  request.onsuccess = () => resolve(request.result);
  request.onerror = () => reject(request.error);
});

This creates a database with a single object store called "settings". The upgrade handler only runs when the version number changes, which is how you modify schemas over time without corrupting existing data.


Writing Data That Survives Reloads

To store user preferences, UI state, or last-opened files:

const tx = db.transaction("settings", "readwrite");
const store = tx.objectStore("settings");

await store.put({ id: "theme", value: "dark" });
await tx.done;

Reload the page. Close the browser. Reboot the machine. The state is still there — instantly, locally, with zero network.


Reading Data (Instant, Local, Offline)

Reading is even simpler:

const tx = db.transaction("settings", "readonly");
const store = tx.objectStore("settings");

const theme = await store.get("theme");
console.log(theme.value); // "dark"

No network hop. No latency. No backend required. Just instant local reads.


Storing File Handles (The Secret Power Move)

This is where IndexedDB becomes essential for local dashboards. You can store FileSystemFileHandle and FileSystemDirectoryHandle objects directly:

await store.put({
  id: "workspace",
  handle: directoryHandle
});

On the next page load, retrieve the handle and request permission again:

const entry = await store.get("workspace");
const handle = entry.handle;

const permission = await handle.requestPermission({ mode: "readwrite" });

This is how your dashboard remembers which folder the user selected, restores access without re-prompting, and behaves like a real desktop application. This is the architectural glue between File System Access and persistent UX.


Why IndexedDB Matters for Local Dashboards

Because it gives you persistent state, offline capability, structured storage, large object support, file handle persistence, and zero backend dependency. IndexedDB is the difference between "a clever HTML file that does things" and "a real application that remembers everything." This is the database layer of the local-first model.


What Comes Next

With files (File System API) and state (IndexedDB) covered, the next pillar is performance — processing large datasets without freezing the UI. That means Web Workers and Streams API. Coming up next.


Aaron Rose is a software engineer and technology writer at tech-reader.blog

Catch up on the latest explainer videos, podcasts, and industry discussions below.


Popular posts from this blog

Insight: The Great Minimal OS Showdown—DietPi vs Raspberry Pi OS Lite

Running AI Models on Raspberry Pi 5 (8GB RAM): What Works and What Doesn't

Raspberry Pi Connect vs. RealVNC: A Comprehensive Comparison