IndexedDB hello world

IndexedDB is one kind of storage provided by browsers, alongside cookies, LocalStorage, and AppCache. You may have heard that IndexedDB is a key-value store. This may fool you into thinking that the IndexedDB API is similar to LocalStorage, which does not have much more than getItem and putItem. No! Compared to LocalStorage, IndexedDB has many additional concepts: multiple DBs, multiple “object stores”, indexes, primary keys, DB upgrades, transactions. Consequently, the “hello world” is pretty long.

This page has created a database called "testdb", which has one table called "employees". You can see this in Chrome by opening Developer Tools, going to Application, then IndexedDB.

The following form sets/gets values in the localStorage for this website (jameshfisher.com).

Like LocalStorage, IndexedDB storage is per-origin (for this site, https://jameshfisher.com:443). Unlike LocalStorage, each origin can create many key-value stores. Each origin can have multiple databases. The database "exampledb" is “opened” with:

const req = indexedDB.open("exampledb", 1);
req.addEventListener("upgradeneeded", (ev) => {
  const db = req.result;
  // ...
});
req.addEventListener("success", () => {
  const db = req.result;
  // ...
});

There are two important listeners: success and upgradeneeded. Databases in IndexedDB are versioned, and upgradeneeded is used for database migrations. A database version is a natural number, beginning at version 1. If you view "exampledb" in Developer Tools, you’ll see it’s at version 1. A call to indexedDB.open("exampledb", n) compares n with the current version. If current_version < n, the upgradeneeded listener is called.

Each database can have multiple object stores, which are similar to tables in an RDBMS. The "exampledb" database has one object store called "employees". Object stores can only be created in the upgradeneeded listener (similar to how tables in an RDBMS are conventionally only created in migrations). Here’s the upgradeneeded listener for "exampledb":

req.addEventListener("upgradeneeded", (ev) => {
  const db = req.result;
  db.createObjectStore("employees", { keyPath: "name" });
});

In the call to createObjectStore, the parameter keyPath defines the primary key for objects in the store. With keyPath set to "name", the object {name: "Jim", position: "CEO"} will be stored under the key "Jim".

The other important listener is for success, which gives us our database handle:

req.addEventListener("success", () => {
  const db = req.result;
  // ...
});

With our db handle in place, we can get/put items:

function putEmployee(employee) {
  const tx = db.transaction(["employees"], "readwrite");
  const employeeTable = tx.objectStore("employees");
  employeeTable.put(employee);
}

function getEmployee(name, cb) {
  valEl.value = localStorage.getItem(keyEl.value);
  const tx = db.transaction(["employees"], "readonly");
  const employeeTable = tx.objectStore("employees");
  const getReq = employeeTable.get(name);
  getReq.addEventListener("success", () => cb(getReq.result));
}

I wrote this because I felt like it. This post is not associated with my employer.