オフラインでシームレスに動作するアプリケーションを構築することは、現代のウェブ開発において重要な要件となっています。IndexedDBは、ブラウザ内で大量のデータを直接保存および管理したい開発者にとって強力なソリューションを提供します。このクライアントサイドのNoSQLデータベースは、効率的なデータ処理を可能にし、オフライン機能が必要なウェブアプリケーションに最適です。この記事では、IndexedDBの基本、主要な機能、およびウェブプロジェクトでの設定と使用方法のステップを説明します。
IndexedDBとは?
IndexedDBは、ウェブアプリケーションがブラウザ内で直接構造化データを保存および管理できる強力なクライアントサイドのNoSQLデータベースです。これは、ローカルでの大規模データセットの処理に効率的なソリューションを提供し、オフライン機能やデータ重視のアプリケーションに最適です。

IndexedDBの主な利点の1つは、オフラインウェブアプリケーションをサポートできることです。開発者は大量のデータを保存し、迅速な取得のためにインデックスを使用し、トランザクションを管理し、カーソルを使用してデータをナビゲートできます。これらの機能により、ウェブアプリでの複雑なデータ処理に適した選択肢となります。
Chrome、Firefox、Safari、Edgeを含むほとんどの現代のウェブブラウザは、IndexedDBをサポートしています。ただし、ウェブアプリケーションをターゲットにするブラウザとIndexedDBのバージョンが互換性があることを確認することが重要です。
IndexedDBの使用を開始するには、最初のステップとしてデータベースへの接続を開く必要があります。これにより、データベースを作成し、インタラクションを行うことが可能となります。

IndexedDBの始め方:包括的ガイド
IndexedDBは、ウェブアプリケーション向けの強力なクライアントサイドストレージソリューションです。このガイドでは、基本的な設定から高度なテクニック、実世界での実装までのすべてを説明します。
IndexedDBの設定
データベースの作成とオープン
IndexedDBで新しいデータベースを作成するには、indexedDB.open()
メソッドを使用します:
const request = indexedDB.open("MyDatabase", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// ここでオブジェクトストアとインデックスを作成します
};
このメソッドは、データベース名とバージョン番号の2つのパラメータを取ります。既存のデータベースを開くには、バージョン番号を指定せずにメソッドを呼び出すことができます。
データベースのバージョニング
IndexedDBは、スキーマの変更を処理するためのバージョニングをサポートしています。既存のデータベースよりも高いバージョン番号のデータベースを開くと、onupgradeneeded
イベントがトリガーされ、データベーススキーマを更新できます。
オブジェクトストアとインデックスの作成
オブジェクトストアは、IndexedDB内のデータのコンテナです:
request.onupgradeneeded = (event) => {
const db = event.target.result;
// キーパスを持つオブジェクトストアを作成
const objectStore = db.createObjectStore("customers", { keyPath: "id" });
// 効率的なクエリ用のインデックスを作成
objectStore.createIndex("name", "name", { unique: false });
};
トランザクションの操作
トランザクションの基本を理解する
IndexedDBのトランザクションは、複数の操作を単一のアトミックな単位にグループ化することでデータの整合性を保ちます。すべての変更が適用されるか、全く適用されないことを保証します。
トランザクションの作成と管理
const transaction = db.transaction(["customers"], "readwrite");
const objectStore = transaction.objectStore("customers");
transaction.oncomplete = (event) => {
console.log("トランザクションが正常に完了しました");
};
transaction.onerror = (event) => {
console.error("トランザクションが失敗しました");
};
エラーハンドリングとロールバック
操作が失敗した場合は、abort()
メソッドを使用してトランザクション全体をロールバックできます:
try {
// 操作を実行
} catch (error) {
transaction.abort();
console.error("トランザクションが中断されました:", error);
}
IndexedDBにおけるデータ操作
データの追加
const customerData = { id: "00001", name: "ジョン・ドウ", email: "john@example.com" };
const request = objectStore.add(customerData);
request.onsuccess = (event) => {
console.log("データが正常に追加されました");
};
データの取得
キーによる基本的なデータ取得:
const request = objectStore.get("00001");
request.onsuccess = (event) => {
console.log("顧客データ:", request.result);
};
インデックスを使用したフィルタリング
const index = objectStore.index("name");
const request = index.get("ジョン・ドウ");
request.onsuccess = (event) => {
console.log("名前で見つかりました:", request.result);
};
高度なクエリ手法
複雑なクエリのために、IndexedDBはopenCursor()
やIDBKeyRange
のようなメソッドを使用して範囲クエリや複合クエリを提供します:
const range = IDBKeyRange.bound("A", "F"); // AからFで始まる名前
const request = index.openCursor(range);
レコードの更新
const updateData = { id: "00001", name: "ジョン・スミス", email: "john@example.com" };
const request = objectStore.put(updateData);
レコードの削除
const request = objectStore.delete("00001");
request.onsuccess = (event) => {
console.log("レコードが削除されました");
};
カーソルを使った操作
カーソル機能の理解
カーソルを使用すると、オブジェクトストアやインデックス内のレコードを効率的に反復処理することができ、データを遍歴および操作する方法を提供します。
レコードのナビゲート
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log("キー:", cursor.key, "値:", cursor.value);
cursor.continue(); // 次のレコードに移動
} else {
console.log("レコードがありません");
}
};
カーソルを使ったデータの変更
const transaction = db.transaction(["customers"], "readwrite");
const objectStore = transaction.objectStore("customers");
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
if (cursor.value.status === "inactive") {
const updateData = cursor.value;
updateData.status = "active";
cursor.update(updateData);
}
cursor.continue();
}
};
スキーマ管理とアップグレード
データベーススキーマのアップグレード
アプリケーションが進化する際に、データベースのスキーマを変更する必要があるかもしれません:
const request = indexedDB.open("MyDatabase", 2); // バージョン番号を上げる
request.onupgradeneeded = (event) => {
const db = event.target.result;
// オブジェクトストアが存在するかチェック
if (!db.objectStoreNames.contains("newStore")) {
db.createObjectStore("newStore", { keyPath: "id" });
}
};
アップグレード中のデータ移行
request.onupgradeneeded = (event) => {
const db = event.target.result;
const oldVersion = event.oldVersion;
if (oldVersion < 1) {
// 初版の設定
}
if (oldVersion < 2) {
// 新しいスキーマにデータを移行
const transaction = event.target.transaction;
const oldStore = transaction.objectStore("oldStore");
const newStore = db.createObjectStore("newStore", { keyPath: "id" });
oldStore.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
newStore.add(cursor.value);
cursor.continue();
}
};
}
};
パフォーマンス最適化
効率的なバルク操作
パフォーマンスを向上させるために、複数のレコードを扱う際にはバルク操作を使用してください:
const transaction = db.transaction(["customers"], "readwrite");
const store = transaction.objectStore("customers");
// 単一のトランザクションで複数のレコードを追加
customerList.forEach(customer => {
store.add(customer);
});
クエリの高速化のためのインデックス利用
迅速なデータ取得を保証するために、頻繁にクエリされるプロパティにインデックスを作成してください:
objectStore.createIndex("email", "email", { unique: true });
objectStore.createIndex("lastLogin", "lastLogin", { unique: false });
接続管理のベストプラクティス
必要なときだけ接続を開き、終了したら閉じるようにしてください:
let db;
function openDB() {
const request = indexedDB.open("MyDatabase", 1);
request.onsuccess = (event) => {
db = event.target.result;
};
return request;
}
// データベースが終わったら
function closeDB() {
if (db) {
db.close();
db = null;
}
}
実世界の例:オフラインサポートを持つタスクマネージャー
データベースのセットアップ
const request = indexedDB.open("TaskManagerDB", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const taskStore = db.createObjectStore("tasks", { keyPath: "id", autoIncrement: true });
// クエリ用のインデックスを作成
taskStore.createIndex("status", "status", { unique: false });
taskStore.createIndex("dueDate", "dueDate", { unique: false });
};
タスクの追加
function addTask(taskData) {
const transaction = db.transaction(["tasks"], "readwrite");
const taskStore = transaction.objectStore("tasks");
return new Promise((resolve, reject) => {
const request = taskStore.add({
title: taskData.title,
description: taskData.description,
status: "pending",
dueDate: taskData.dueDate,
createdAt: new Date()
});
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
タスクの取得と表示
function getAllTasks() {
const transaction = db.transaction(["tasks"], "readonly");
const taskStore = transaction.objectStore("tasks");
return new Promise((resolve, reject) => {
const request = taskStore.openCursor();
const tasks = [];
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
tasks.push(cursor.value);
cursor.continue();
} else {
resolve(tasks);
}
};
request.onerror = () => reject(request.error);
});
}
タスクの更新と削除
function updateTaskStatus(id, newStatus) {
const transaction = db.transaction(["tasks"], "readwrite");
const taskStore = transaction.objectStore("tasks");
return new Promise((resolve, reject) => {
const getRequest = taskStore.get(id);
getRequest.onsuccess = () => {
const task = getRequest.result;
task.status = newStatus;
task.updatedAt = new Date();
const updateRequest = taskStore.put(task);
updateRequest.onsuccess = () => resolve(true);
updateRequest.onerror = () => reject(updateRequest.error);
};
getRequest.onerror = () => reject(getRequest.error);
});
}
サーバーとの同期
function syncWithServer() {
if (!navigator.onLine) {
return Promise.reject(new Error("インターネット接続がありません"));
}
return getAllTasks()
.then(tasks => {
// 同期が必要なタスクをフィルタリング
const unsynced = tasks.filter(task => !task.synced);
// fetch APIを使用してサーバーに送信
return fetch('https://api.example.com/tasks/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(unsynced)
});
})
.then(response => response.json())
.then(result => {
// タスクを同期済みとしてマーク
const transaction = db.transaction(["tasks"], "readwrite");
const taskStore = transaction.objectStore("tasks");
result.syncedIds.forEach(id => {
const request = taskStore.get(id);
request.onsuccess = () => {
const task = request.result;
task.synced = true;
taskStore.put(task);
};
});
return result;
});
}
// オンラインイベントをリッスンして自動的に同期
window.addEventListener('online', syncWithServer);
IndexedDBでのAPI管理のためのApidogの統合
クライアントサイドストレージにIndexedDBを使用してアプリケーションを構築する際、データの同期のためにバックエンドAPIと連携する必要があることがよくあります。Apidogは、これらのAPIインタラクションを管理するためのシームレスなソリューションを提供します。

ApidogがIndexedDB開発を強化する理由
IndexedDBを使用してオフライン対応アプリケーションを開発する際、Apidogは幾つかの利点を提供します:
リアルタイム同期:Apidogは、データの同期に使用するAPIエンドポイントが常に適切に設定され、テストされていることを保証し、アプリがオンラインになるときの統合の問題を排除します。
モックAPI応答:オフライン機能を開発する際、Apidogのスマートモックエンジンを使用すると、ライブサーバーなしでIndexedDBの同期ロジックをテストするためにAPI応答をシミュレートできます。

共同API設計:チームがフロントエンドストレージとバックエンドAPIに同時に取り組んでいるとき、ApidogはAPI仕様に関するリアルタイムのコラボレーションを促進します。

Apidogを開発ワークフローに統合することで、クライアントサイドのストレージとサーバーサイドの処理の間にシームレスな橋を築き、アプリケーションをより堅牢でメンテナンスしやすくします。