Webアプリケーションでデータを保存する方法はいくつかありますが、IndexedDBはその中でも特に高機能なものです。CookieやLocalStorageは比較的よく知られていますが、IndexedDBはそれらを凌ぐ機能を持っています。

今回は、このIndexedDBの基本的な使い方と活用方法について、初心者にも分かりやすく解説します。

IndexedDB API - Web API | MDN

IndexedDBとは

IndexedDBはその名の通り、データベースの一種です。Webブラウザ上で動作するデータベースで、データを保存・取得・削除ができます。IndexedDBは以下の特徴を持っています。

  • キーと値のペアをデータを保存
  • インデックスを作成して高速に検索
  • トランザクションをサポートしてデータの整合性を保つ
  • スキーマのバージョン管理ができる
  • ほとんどの操作が非同期で行われる

IndexedDBは、キーバリューストア(KVS)という点ではLocalStorageと似ています。しかし、LocalStorageが文字列しか保存できないのに対し、IndexedDBは値がオブジェクトである点が大きな違いです。

IndexedDBの基本的な使い方

では、ここからIndexedDBの基本的な使い方を解説します。

データベースを開く

IndexedDBを使うためには、まずindexedDB.open()メソッドを使ってデータベースを開きます。

const request = indexedDB.open("myDatabase", 1);

ここで、第1引数はデータベース名、第2引数はデータベースのバージョンです。

そして、データベースが開けたかどうかはコールバックで確認します。

// 成功の場合
request.onsuccess = function(event) {
    const db = event.target.result;
    console.log("データベースが開けました");
};
// エラーの場合
request.onerror = function(event) {
    console.log("データベースが開けませんでした");
};

オブジェクトストアを定義

次に、データベースの中に、オブジェクトストア(テーブルのようなもの)を定義します。 keyPath はプライマリキーを指定します。この処理は、 request.onupgradeneeded の中で行います。

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

ここで、createObjectStore()メソッドの第1引数はオブジェクトストア名、第2引数はオプションです。keyPathはプライマリキーとなるプロパティ名を指定します。
オブジェクトストアにインデックスを定義することもできます。

objectStore.createIndex("name", "name", { unique: false });

request.onupgradeneeded はバージョン番号が変化した際に呼び出されます。

データを追加する

オブジェクトストアにデータを追加するには、トランザクションを使います。例えば、以下のようなデータを追加してみましょう。

const customers = [
    {
        "id": 1,
        "name": "Alice",
        "age": 20
    },
    {
        "id": 2,
        "name": "Bob",
        "age": 30
    }
]

そして、トランザクションを使って、データを追加します。

transaction メソッドの1つ目の引数はオブジェクトストアの名前(文字列、または配列)、2つ目の引数はモードです。読み書きする場合には readwrite を指定しますが、取得だけの場合は省略可能です。

const customerObjectStore = db
    .transaction("customers", "readwrite")
    .objectStore("customers");
customerData.forEach((customer) => {
    customerObjectStore.add(customer);
});

データを検索する

データを検索する場合もトランザクションを使います。

const transaction = db.transaction(["customers"]);
const objectStore = transaction.objectStore("customers");
const request = objectStore.get("1");
request.onerror = (event) => {
  // エラー処理
};
request.onsuccess = (event) => {
  console.log(ID for 2 is ${request.result.name});
};

ここでは、get()メソッドを使ってプライマリキーが"1"のデータを取得しています。

データを更新する

データを更新する場合には、 put メソッドを使います。

const request = db
    .transaction(["customers"], "readwrite")
    .objectStore("customers")
    .put({ id: 1, name: "Alice", age: 21 });

データを削除する

データを削除する場合も、トランザクションを開始し、オブジェクトストアにアクセスしてdelete()メソッドを使います。

const request = db
  .transaction(["customers"], "readwrite")
  .objectStore("customers")
  .delete("1");
request.onsuccess = (event) => {
  // 削除完了した時のコールバック
};

これがIndexedDBの基本的な使い方です。

IndexedDBの実用的な使い方

ラッパーライブラリを使ってシンプルに

IndexedDBは非同期処理が基本で、onsuccessなどのイベントハンドラを定義する必要があります。これは初心者にとって少し複雑に感じるかもしれません。現代的なJavaScriptでは、async/awaitやPromiseを使うのが一般的ですね。

そこでおすすめなのが、IndexedDBのラッパーライブラリを使うことです。特に「Dexie.js」は使いやすいライブラリで、シンプルなコードでIndexedDBを操作できます。

特に Dexie.js は使いやすいライブラリです。以下はそのコードです。

const db = new Dexie("MyDatabase");

// テーブルの定義(++id は自動採番)
db.version(1).stores({
    friends: "++id, name, age"
});

// データの検索
const oldFriends = await db.friends
    .where("age").above(75)
    .toArray();

// データの追加
await db.friends.add({
    name: "Camilla",
    age: 25,
    street: "East 13:th Street",
    picture: await getBlob("camilla.png")
});

他にも「PouchDB」や「idb」など、便利なライブラリがあります。これらを使えば、IndexedDBをストレスなく使いこなせるでしょう。

オフラインアプリケーションを作ろう

IndexedDBの大きな魅力は、ブラウザ内で動作するため、ネットワーク接続が不要なことです。これを利用して、オフラインでも動作するアプリケーションを作ることができます。

例えば、クラウドサービスからデータをまとめてダウンロードし、実際の動作はIndexedDBで行うといった使い方ができます。この場合、クラウドとIndexedDBの同期処理と、フロントエンドとIndexedDBとで処理を分けることが重要です。それさえできれば、オフラインアプリケーションを作るのは難しくありません。

ネットワークとデータベースを切り離せると、万一クラウドサービスが落ちている際にも、アプリケーションは動作を続けられます。これは、ユーザーエクスペリエンスの向上につながります。

Web Workerと組み合わせて高速処理

IndexedDBは、Web Workerと組み合わせることで、さらに強力なツールになります。Web Workerはブラウザのバックグラウンドで動作するJavaScriptで、重い処理を担当させることができます。
例えば、フロントエンドで処理が必要なデータをIndexedDBに登録し、Web Workerを呼び出します。Web WorkerはIndexedDBのデータを参照・更新します。IndexedDBではバイナリファイルも扱えるので、画像の処理をWeb Workerで行うこともできます。

このように、IndexedDBとWeb Workerを組み合わせることで、アプリケーションのパフォーマンスを大幅に向上させることができるのです。

まとめ

IndexedDBは、Web開発者にとって非常に便利なツールです。ラッパーライブラリを使えばシンプルに、オフラインアプリケーションを作れば利便性が向上し、Web Workerと組み合わせれば高速処理が可能になります。ぜひ、IndexedDBを使いこなして、より良いアプリケーションを作ってみてください!