導入
フロントエンド開発において、ブラウザ上のデータ保存といえば localStorage が一般的ですが、容量制限や同期処理によるメインスレッドのブロックという課題があります。IndexedDB は非同期で動作する大規模な構造化データストレージであり、IDBDatabase インターフェースはその中核を担います。オフライン対応アプリや、大量のキャッシュデータを扱うプロジェクトにおいて、IDBDatabase を正しく理解し使いこなすことは、UXを向上させるための重要なスキルです。
基礎知識
IDBDatabase とは、IndexedDB データベースへの接続を表すオブジェクトです。IndexedDB は「キーバリュー型」のデータベースですが、オブジェクトストア(RDBMSのテーブルに相当)を作成することで複雑なデータ構造も扱えます。重要な点は、操作がすべて「トランザクション」単位で行われることです。データの一貫性を保つため、読み書きの処理は必ずトランザクションを開始してから実行する必要があります。
実装/解決策
IDBDatabase を操作する一般的な流れは以下の通りです。
1. open() メソッドでデータベース接続を開始する。
2. onupgradeneeded イベントでスキーマ(オブジェクトストア)の定義を行う。
3. onsuccess イベントで取得した IDBDatabase インスタンスに対してトランザクションを開始する。
4. トランザクション経由でオブジェクトストアにアクセスし、データを操作する。
サンプルプログラム
以下のコードは、データベースを開き、データを保存するまでの基本的な実装例です。
// 1. データベース接続を開く(バージョン1)
const request = indexedDB.open('MyDatabase', 1);
// 2. スキーマ定義(データベース初回作成時やバージョン更新時に呼ばれる)
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 'users' という名前のオブジェクトストアを作成(キーは 'id')
if (!db.objectStoreNames.contains('users')) {
db.createObjectStore('users', { keyPath: 'id' });
}
};
// 3. 成功時の処理
request.onsuccess = (event) => {
const db = event.target.result;
// トランザクションを開始(読み書きモード)
const transaction = db.transaction(['users'], 'readwrite');
const store = transaction.objectStore('users');
// データの追加
const user = { id: 1, name: 'フロントエンド太郎', role: 'Engineer' };
const addRequest = store.add(user);
addRequest.onsuccess = () => console.log('データ保存成功');
addRequest.onerror = (e) => console.error('保存エラー', e);
};
応用・注意点
実務で IndexedDB を扱う際に陥りやすいポイントがいくつかあります。
トランザクションの終了待ち: トランザクションは非同期です。トランザクションが完了したタイミングで処理を行いたい場合は、transaction.oncomplete イベントを監視してください。
バージョン管理: データベース構造を変更する際は、必ずバージョン番号を上げる必要があります。既存ユーザーのデータを壊さないよう、マイグレーション処理は慎重に設計してください。
Promise ラッパーの活用: 生の IDBDatabase API はイベントベースで記述が長くなりがちです。実務では idb ライブラリのような、Promise ベースで操作できる軽量なラッパーライブラリを導入することで、async/await を使った可読性の高いコードを書くことが推奨されます。
IndexedDB は強力ですが、複雑なクエリが必要な場合は、IndexedDB を内部で利用しているライブラリ(Dexie.js など)の採用も検討し、要件に合わせて使い分けるのがエンジニアとしての賢い選択です。