IndexedDBとは?

IndexedDBは、ブラウザに組み込まれた非同期のキー・バリュー型データベースです。

LocalStorageやSessionStorageが「文字列のみ」しか扱えないのに対し、IndexedDBは構造化されたデータをそのまま保存できます。

また、

  • オブジェクトストア(Object Store)という単位でデータを管理
  • インデックスによる高速検索が可能

といった特徴があります。

👉 大量データ+検索が必要なクライアントアプリ向けの仕組みです。


構造化データとは?

構造化データとは、「形を保ったまま扱えるデータ」のことです。

IndexedDBでは、以下のようなデータをそのまま保存できます:

  • オブジェクト
  • 配列
  • Map / Set
  • Date
  • Blob / File
  • ArrayBuffer など

これは 構造化クローンアルゴリズム によって実現されています。

👉 JSON.stringify のような変換は不要です。


IndexedDBはオブジェクトデータベース?

結論から言うと:

👉 完全なオブジェクトデータベースではありません。

理由は以下です。

✔ 保存できるもの

  • データ(プロパティ)

❌ 保存できないもの

  • 関数
  • DOM要素
  • prototype(クラスの振る舞い)

つまり:

class User {
  greet() { console.log("hello"); }
}

これを保存すると、

👉 メソッドは消え、ただのプレーンオブジェクトになります。

👉 正確な理解:

「オブジェクトを保存できるキー・バリュー型DB」


特徴

特徴 説明
非同期API イベントベース(またはPromiseラッパー)で動作し、UIをブロックしない
構造化データの保存 オブジェクトをそのまま保存できる(※メソッドは除く)
大容量対応 数十MB〜数百MB以上(ブラウザ・ユーザー許可に依存)
インデックス機能 特定プロパティで高速検索
トランザクション 複数操作をまとめて安全に実行

👉 ブラウザ上で“小さなDBアプリ”が作れるレベルの機能


IndexedDB の全体像

データベースの中に、オブジェクトストアという器があり、その器の中に個々のオブジェクトがデータとして格納されています。

(図1)IndexedDBのイメージ

👉 RDBに少し似ていますが、もっと柔軟です。


IndexedDB の4つの基本概念

LocalStorageやSessionStorageとは、異なり本格的なデータベースの構造を持ち、単純な関数アクセスとは異なります。

IndexedDBには、4つの基本概念が存在します。

  1. データベース
  2. オブジェクトストア
  3. インデックス
  4. トランザクション

1. データベース(Database)

IndexedDB 全体の「大きな箱」。
アプリごとに 1 つ以上作れる。 中には複数の「オブジェクトストア」が入る。

例:MyAppDB

バージョン番号で構造を管理する(onupgradeneeded が動く)

2. オブジェクトストア(Object Store)

データを入れる「引き出し」。
RDB の「テーブル」に近いが、もっとゆるい。

JSON オブジェクトをそのまま保存できる。

keyPath(主キー)を決められる。

スキーマレス(保存するオブジェクトの形は自由)。プロパティ構造の異なるオブジェクトでも同一ストアに保存できます。

👉 ただし、主キーは必要。

3. インデックス(Index)

検索を速くする「見出し」。
オブジェクトストアの中の特定のプロパティに対して作る。

例:保存されているデータが下記のような場合、

{ name: 山田太郎 email: yamada@example.com }

email にインデックスを作ると、

store.index("email").get("yamada@example.com") により、高速にアクセスが可能になる。

unique にすると「重複禁止」もできる。

👉 スキーマレスの為、インデックス対照となるプロパティを持たないオブジェクトは検索対象外

4. トランザクション(Transaction)

「まとめて安全に処理する」ための作業単位。複数のデータアクセスを1まとめに行う。エラー発生時は、同一トランザクション内の全ての処理が巻き戻される。

IndexedDB の操作は必ずトランザクションの中で行う。

読み取り専用(readonly)

読み書き(readwrite)

👉 トランザクションが終わると自動で閉じる。


基本的な使い方(簡略版)

LocalStorageやSessionStorageとは異なり、データベースである為、アクセスには、しっかりとした手続きを踏む必要があります。

① データベースを開く ② 初回、バージョンアップ時は、オブジェクトストアの作成 ③ トランザクションの開始 ④ データアクセス

// ① DBを開く(なければ作成)
const request = indexedDB.open('MyDatabase', 1);

// ② 初回 or バージョンアップ時
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  db.createObjectStore('notes', { keyPath: 'id' });
};

// ③ DBオープン成功
request.onsuccess = (event) => {
  const db = event.target.result;

  // ④ トランザクション開始
  const tx = db.transaction('notes', 'readwrite');
  const store = tx.objectStore('notes');

  // ⑤ データ操作
  store.add({
    id: 1,
    title: 'こんにちは',
    content: 'これはメモです'
  });

  tx.oncomplete = () => {
    console.log('保存完了');
  };

  tx.onerror = () => {
    console.error('エラー発生');
  };
};

👉 補足(重要)

add は重複キーでエラー 上書きしたい場合は put を使う

非同期なので、トランザクションの寿命が短い(ここが落とし穴)

👉 重要ポイント(初心者つまずき)

トランザクション開始後に、非同期処理を呼び出すと、処理中にトランザクションの寿命が切れることがある。


トランザクションの寿命が切れるとは?(重要)

例えばこのコード:

const tx = db.transaction('notes', 'readwrite');
const store = tx.objectStore('notes');

await someAsyncFunction(); // ←ここが問題

store.add({ id: 1, title: 'NG例' });

一見問題なさそうですが…

❌ 何が起きているか

  1. トランザクション開始
  2. await に到達 → 処理が一旦中断
  3. JavaScriptの実行コンテキストが終了 👉 IndexedDBが「もう処理終わった」と判断
  4. トランザクション自動終了
  5. store.add() 実行時には… 👉 TransactionInactiveError が発生

■ イメージ(超重要)

[トランザクション開始] ↓ 同期処理中 ← OK(生きてる) ↓ await / setTimeout ↓ イベントループに戻る ↓ ❌ トランザクション終了(ここが罠)

👉 IndexedDBのトランザクションは、「今の同期処理の流れの中だけ有効」な為、一度でも処理が途切れると終了します。

■ 正しい書き方

✔ パターン①:同期的に全部書く

const tx = db.transaction('notes', 'readwrite');
const store = tx.objectStore('notes');

store.add({ id: 1 });
store.add({ id: 2 });
store.add({ id: 3 });

✔ パターン②:非同期処理は外で済ませる

const data = await fetchData(); // ←先にやる

const tx = db.transaction('notes', 'readwrite');
const store = tx.objectStore('notes');

store.add(data); // OK


IndexedDBは、どんなときに使う?

  • オフライン対応(PWA)
  • 大量データ(メモ、履歴、ログ)
  • APIレスポンスのキャッシュ
  • クライアントDB的な用途

メリット

  • 構造化データをそのまま保存できる
  • インデックスによる高速検索
  • 主要ブラウザで利用可能で、ブラウザ間の互換性が高い(Chrome, Firefox, Safariなど)

デメリット

  • APIが複雑(イベント駆動)で、学習コストが高い
  • デバッグがやや難しい
  • トランザクションの扱いが難しい(トランザクションは同期処理内の短い間隔で完結するような設計が必要)

👉 実務ではラッパーライブラリ(Dexie, idbなど)を使うことも多い。


セキュリティ

■ 強い点

  • 任意コード(関数)は保存できない
  • 同一オリジン制約で保護される

👉 ただし「安全」ではなく「限定的に安全」

■ 弱い点(重要)

  • JavaScriptから自由にアクセス可能
  • XSSがあると全データが取得される

👉 ここが最大のリスク

■ 実務で必要な対策

  • HTTPSの使用
  • CSP(Content Security Policy)の設定
  • コードのXSS対策(最重要)

👉 使用上、制限はないが、安全な利用の為には、検討・対策が必要