IndexedDB

Open a notes database

Create the field-notes IndexedDB database and its first object store with a small promise wrapper.

Saved notes are structured records, and there will be many of them. That’s the job IndexedDB was made for. It’s the browser’s real database: asynchronous, indexed, and able to store JavaScript objects without turning them into strings first.

Why a wrapper

The native IndexedDB API is event-based. You call a method, get a request object back, and attach onsuccess and onerror handlers. It works, but it’s verbose, and it predates promises.

We’ll use the small idb library instead. It wraps the same objects in promises, so we can use await, while keeping every IndexedDB concept visible: databases, versions, object stores, transactions. You learn the real thing, with less typing. Install it with npm install idb.

Open the database

Opening a database means naming it and giving it a version number. The first time the browser sees that name, or when the version is higher than the stored one, it runs the upgrade callback. That’s the only place where you can create object stores.

import { openDB } from 'idb'

export const db = await openDB('field-notes', 1, {
  upgrade(db) {
    db.createObjectStore('notes', { keyPath: 'id' })
  },
})

An object store is a collection of records, a bit like a table without a fixed set of columns. keyPath: 'id' tells IndexedDB to use each record’s id property as its primary key. We’ll generate those IDs ourselves in the next lesson.

After this runs, open the Application panel and expand IndexedDB. You’ll see field-notes with version 1 and one store named notes, empty for now.

The upgrade runs once

Reload the page. The upgrade callback doesn’t run again. The browser compares the requested version 1 with the stored version 1, sees they match, and skips it. Reload again and it’s the same. The store you created is still there.

This is the schema rule for IndexedDB: structure changes only happen inside a version upgrade. You can’t create a store from a click handler. If you try, you get an InvalidStateError. Later, when we add an index, we’ll bump the version to 2 and the callback runs once more.

Only in the browser

IndexedDB exists in browsers, not in Node.js. If your app uses server-side rendering, the top-level await openDB() above will throw on the server with openDB is not a function or indexedDB is not defined.

Keep this code in a module that only runs on the client, or guard it with if (typeof indexedDB !== 'undefined'). Server-rendered HTML doesn’t need the database anyway. It needs it after hydration, when the user starts saving notes.

Install idb, open version 1, and look at the result in DevTools. Then reload twice and check the store is still there and the upgrade callback didn’t fire. Adding a console.log('upgrading') inside it makes that easy to confirm.

Lesson completed