はじめに

IndexedDB は、ブラウザにデータを保存するための強力な仕組みです。IndexedDBの詳細については、過去記事「データ保存技術/IndexedDBとは」を参照してください。

IndexedDB は、非常に便利ではありますが、標準 API は、イベント駆動で扱いにくく、次のような問題があります。

  • トランザクションの寿命が短い
  • 非同期処理が複雑(Promise ではなくイベント)
  • カーソル操作が冗長
  • エラーハンドリングが難しい

そこで登場するのが idb公式サイト)です。

idb は、Google の Jake Archibald さんが作った IndexedDB の Promise ラッパー で、標準 API の複雑さをほぼ完全に隠してくれます。

👉 IndexedDB を “モダンに” 使うなら、idb 一択です。

開発者の方、関係者の方には、この場をお借りして、深くお礼を申し上げたいと思います。


idb の import

idb を利用するためには、ライブラリーの読み込みが必要です。下記に読み込み例を示します。

① CDN で読み込む(最も簡単)

<script type="module">
  import { openDB } from "https://cdn.jsdelivr.net/npm/idb@7/build/esm/index.js";
</script>

② npm でインストール(本格的なプロジェクト向け)

npm install idb
import { openDB } from "idb";

👉 idb の中心は openDB()。これさえ理解すれば、IndexedDB の 80% は扱えます。


openDB の基本

最小の DB を開くコードは次のとおりです。

const db = await openDB("MyDB", 1, {
  upgrade(db) {
    db.createObjectStore("users", { keyPath: "id" });
  }
});

openDB の引数

引数 意味
第1引数 DB 名
第2引数 バージョン番号
第3引数 スキーマ変更時に呼ばれる upgrade 関数

upgrade の役割

  • オブジェクトストアの作成
  • インデックスの作成
  • スキーマ変更(バージョンアップ)

👉 標準の IndexedDB の onupgradeneeded と同じですが、Promise で扱えるため圧倒的に書きやすいです。


トランザクションの操作

標準の IndexedDB では、トランザクションの寿命が短く、非同期処理を挟むとすぐエラーになります。idb では、これが ほぼ自動管理 されます。

idb のトランザクションは自動

基本的に、idb のトランザクションは自動(必要なときだけ 開く)で行われます。

await db.put("users", { id: 1, name: "Kon" });

これだけで内部的に:

  • readwrite トランザクション開始
  • オブジェクトストア取得
  • put 実行
  • commit

が自動で行われます。

尚、下記の様に、明示的に使うことも可能です。

const tx = db.transaction("users", "readwrite");
await tx.store.put({ id: 1, name: "Kon" });
await tx.done;

👉 標準の IndexedDB API より圧倒的に安全で扱いやすいです。


オブジェクトストアの操作

オブジェクトストアの生成や、インデックスの作成、検索といった操作も直観的でわかりやすいです。

作成

upgrade(db) {
  db.createObjectStore("users", { keyPath: "id" });
}

インデックスの作成

upgrade(db) {
  const store = db.createObjectStore("users", { keyPath: "id" });
  store.createIndex("by_name", "name");
}

インデックス検索

const users = await db.getAllFromIndex("users", "by_name", "Kon");

👉 単純な操作であれば、標準の IndexedDB API の複雑なカーソル操作なく、アクセスが可能です。


CRUD 操作(idb 版)

読み書きの操作も、非常にシンプルです。

Create(追加)

await db.add("users", { id: 1, name: "Kon" });

Read(取得)

const user = await db.get("users", 1);

Update(更新)

await db.put("users", { id: 1, name: "Updated" });

Delete(削除)

await db.delete("users", 1);

全件取得

const all = await db.getAll("users");

👉 標準の IndexedDB API の 操作に必要な 10 行のコードが、idb なら 1 行で済みます。


カーソル(Cursor)操作について

カーソルとは、IndexedDB のレコードを 順番に走査する仕組み です。

データをストリーミング的に1行ずつ処理できる為、大量データであっても、破綻することなく処理が可能です。

標準の IndexedDB API のカーソル操作は、少し複雑ですが、idb はそのカーソル操作をシンプルにしてくれます。

標準の IndexedDB API

const tx = db.transaction("users", 'readonly');
const store = tx.objectStore("users");
const request = store.openCursor();

request.onsuccess = (event) => {
  const cursor = event.target.result;
  if (!cursor) return;
  console.log(cursor.key, cursor.value);
  cursor.continue();
}

idb

let cursor = await db.transaction("users").store.openCursor();

while (cursor) {
  console.log(cursor.key, cursor.value);
  cursor = await cursor.continue(); // 次のレコードへ進む
}

👉 標準の IndexedDB API より圧倒的に簡潔に記述できます。


まとめ

  • IndexedDB を “モダンに” 使うなら idb 一択
  • openDB() が中心で、Promise ベースで扱える。
  • トランザクションは自動管理されるため安全
  • CRUD 操作は 1 行で書ける。
  • 中小規模のデータなら getAll() で十分
  • 大量データではカーソルが必須
  • idb はカーソル操作をシンプルにしてくれる。