概要: このチュートリアルでは、IndexedDB と、それを用いてブラウザ内に永続的にデータを保存する方法について学びます。
IndexedDBとは
IndexedDBは、ブラウザに組み込まれた大規模オブジェクトストアです。
IndexedDBを使用すると、キーと値のペアを使用してデータを永続的に保存できます。
値は、JavaScriptのデータ型であれば何でもかまいません。ブール値、数値、文字列、未定義、null、日付、オブジェクト、配列、正規表現、Blob、ファイルなどです。
IndexedDBを使う理由
IndexedDBを使用すると、オンラインでもオフラインでも動作するウェブアプリケーションを作成できます。
大量のデータを保存し、永続的なインターネット接続を必要としないアプリケーションに役立ちます。
たとえば、GoogleドキュメントではIndexedDBを使用してブラウザにドキュメントのキャッシュを保存し、時折サーバーと同期します。これにより、Googleドキュメントのパフォーマンスを向上させながら、ユーザーエクスペリエンスも向上させることができます。
オンラインのメモ帳、クイズ、TODOリスト、コードサンドボックス、CMSなど、IndexedDBを多用する他の種類のアプリケーションも見つかります。
IndexedDBの構造
次の図は、IndexedDBの構造を示しています。

データベース
データベースはIndexedDBの最上位レベルです。データベースには、1つ以上のオブジェクトストアが含まれています。
IndexedDBには、1つ以上のデータベースを含めることができます。一般的に、ウェブアプリケーションごとに1つのデータベースを作成します。
オブジェクトストア
オブジェクトストアは、データと関連するインデックスを保存するために使用できるバケットです。概念的には、SQLデータベースのテーブルに相当します。
オブジェクトストアには、キーと値のペアとして保存されたレコードが含まれています。
インデックス
インデックスを使用すると、オブジェクトのプロパティでデータをクエリできます。
技術的には、親オブジェクトストアと呼ばれるオブジェクトストアにインデックスを作成します。
たとえば、連絡先情報を保存する場合、メールアドレス、氏名、姓にインデックスを作成して、これらのプロパティで連絡先をクエリできるようにすることができます。
IndexedDBの基本概念
以下は、IndexedDBの基本概念を簡単に紹介します。
1) IndexedDBデータベースはキーと値のペアを保存します
localStorageやsessionStorageとは異なり、IndexedDBに保存される値は、オブジェクトやBlobなどの複雑な構造にすることができます。
また、キーはこれらのオブジェクトのプロパティ、またはバイナリオブジェクトにすることができます。
迅速な検索とソートのために、オブジェクトの任意のプロパティを使用するインデックスを作成できます。
2) IndexedDBはトランザクションです
IndexedDBデータベースへの読み取りと書き込みは、常にトランザクション内で行われます。
トランザクションモデルは、ユーザーが同時に2つのタブ/ウィンドウでウェブアプリケーションを開き、同じデータベースへの読み取りと書き込みを実行した場合に、データの整合性を確保します。
3) IndexedDB APIはほとんど非同期です
IndexedDB操作は非同期です。操作が完了し、結果が利用可能になると、DOMイベントを使用して通知します。
4) IndexedDBはNoSQLシステムです
IndexedDBはNoSQLシステムです。つまり、データをクエリするためにSQLを使用しません。代わりに、カーソルを返すクエリを使用します。その後、カーソルを使用して結果セットを反復処理できます。
5) IndexedDBは同一オリジンポリシーに従います
オリジンとは、コードが実行されるドキュメントのURLのドメイン、プロトコル、ポートです。たとえば、https://javascripttutorial.dokyumento.jp
- ドメイン: javascripttutorial.net
- プロトコル: https
- ポート: 443
https://javascripttutorial.dokyumento.jp/dom/
とhttps://javascripttutorial.dokyumento.jp/
は、ドメイン、プロトコル、ポートが同じため、同一オリジンです。
ただし、https://javascripttutorial.dokyumento.jp/
とhttps://javascripttutorial.dokyumento.jp/
は、プロトコルとポートが異なるため、同一オリジンではありません。
https://javascripttutorial.dokyumento.jp | https://javascripttutorial.dokyumento.jp | |
---|---|---|
プロトコル | https | http |
ポート | 443 | 80 |
IndexedDBは同一オリジンポリシーに準拠しています。つまり、各オリジンには独自のデータベースセットがあり、あるオリジンが他のオリジンのデータベースにアクセスすることはできません。
IndexedDBの基本操作
以下では、IndexedDBデータベースの基本的な操作について説明します。
- データベースへの接続を開く。
- オブジェクトストアにオブジェクトを挿入する。
- オブジェクトストアからデータを読み取る。
- カーソルを使用して結果セットを反復処理する。
- オブジェクトストアからオブジェクトを削除する。
IndexedDBでデータベースへの接続を開く前に、まずプロジェクト構造を作成しましょう。
1) プロジェクト構造の作成
まず、indexeddb
という名前の新しいフォルダを作成します。indexeddb
フォルダ内に、js
という名前のサブフォルダを作成します。
次に、indexeddb
フォルダにindex.html
を、js
フォルダにapp.js
を作成します。
第三に、次のようにapp.js
ファイルにリンクする<script>
タグをindex.html
ファイルに配置します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IndexedDB</title>
</head>
<body>
<script src="js/app.js"></script>
</body>
</html>
Code language: HTML, XML (xml)
app.jsでは、すべてのJavaScriptコードをIIFEに配置します。
(function () {
// all the code will be here
// ...
})();
Code language: JavaScript (javascript)
1) IndexedDBのサポートを確認する
次のコードは、ウェブブラウザがIndexedDBをサポートしているかどうかを確認します。
if (!window.indexedDB) {
console.log(`Your browser doesn't support IndexedDB`);
return;
}
Code language: JavaScript (javascript)
ほとんどの最新のウェブブラウザはIndexedDBをサポートしているため、これはもはや必要ないかもしれません。
2) データベースを開く
データベースへの接続を開くには、window.indexedDB
のopen()
メソッドを使用します。
const request = indexedDB.open('CRM', 1);
Code language: JavaScript (javascript)
open()
メソッドは、2つの引数を受け取ります。
- データベース名(
CRM
) - データベースバージョン(
1
)
open()
メソッドは、IDBOpenDBRequest
インターフェースのインスタンスであるリクエストオブジェクトを返します。
open()
メソッドを呼び出すと、成功または失敗する可能性があります。それぞれのケースを処理するには、次のように対応するイベントハンドラーを割り当てることができます。
request.onerror = (event) => {
console.error(`Database error: ${event.target.errorCode}`);
};
request.onsuccess = (event) => {
// add implementation here
};
Code language: JavaScript (javascript)
3) オブジェクトストアを作成する
初めてデータベースを開くと、onupgradeneeded
イベントがトリガーされます。
既存のバージョンより高いバージョンで2回目にデータベースを開くと、onupgradeneeded
イベントもトリガーされます。
初めての場合、onupgradeneeded
イベントハンドラーを使用して、オブジェクトストアとインデックスを初期化できます。
たとえば、次のonupgradeneeded
イベントハンドラーは、Contacts
オブジェクトストアとそのインデックスを作成します。
// create the Contacts object store and indexes
request.onupgradeneeded = (event) => {
let db = event.target.result;
// create the Contacts object store
// with auto-increment id
let store = db.createObjectStore('Contacts', {
autoIncrement: true
});
// create an index on the email property
let index = store.createIndex('email', 'email', {
unique: true
});
};
Code language: JavaScript (javascript)
動作方法。
- まず、
event.target.result
からIDBDatabase
インスタンスを取得し、それをdb
変数に代入します。 - 次に、
createObjectStore()
メソッドを呼び出して、autoincrement
キーを使用してContacts
オブジェクトストアを作成します。つまり、IndexedDBは、Contacts
オブジェクトストアに挿入される新しいオブジェクトごとに、1から始まる自動増分番号をキーとして生成します。 - 第三に、
createIndex()
メソッドを呼び出して、email
プロパティにインデックスを作成します。メールアドレスは一意であるため、インデックスも一意である必要があります。そのためには、createIndex()
メソッドの3番目の引数{ unique: true }
を指定します。
4) オブジェクトストアにデータ挿入する
データベースへの接続が正常に開かれたら、onsuccess
イベントハンドラーでデータを管理できます。
たとえば、オブジェクトをオブジェクトストアに追加するには、次の手順に従います。
- まず、新しいトランザクションを開きます。
- 次に、オブジェクトストアを取得します。
- 第三に、オブジェクトストアの
put()
メソッドを呼び出して新しいレコードを挿入します。 - 最後に、トランザクションが完了したら、データベースへの接続を閉じます。
次のinsertContact()
関数は、Contacts
オブジェクトストアに新しい連絡先を挿入します。
function insertContact(db, contact) {
// create a new transaction
const txn = db.transaction('Contacts', 'readwrite');
// get the Contacts object store
const store = txn.objectStore('Contacts');
//
let query = store.put(contact);
// handle success case
query.onsuccess = function (event) {
console.log(event);
};
// handle the error case
query.onerror = function (event) {
console.log(event.target.errorCode);
}
// close the database once the
// transaction completes
txn.oncomplete = function () {
db.close();
};
}
Code language: JavaScript (javascript)
新しいトランザクションを作成するには、IDBDatabase
オブジェクトのtransaction()
メソッドを呼び出します。
トランザクションは、readwrite
またはreadonly
の2つのモードのいずれかで開くことができます。readwrite
モードでは、データベースへのデータの読み取りと書き込みが可能ですが、readonly
モードでは、データベースからのデータの読み取りのみが可能です。
データベースからデータを読み取る必要がある場合は、readonly
トランザクションを開くのが良い習慣です。
insertContact()
関数を定義した後、次のようにリクエストのonsuccess
イベントハンドラーで呼び出して、1つ以上の連絡先を挿入できます。
request.onsuccess = (event) => {
const db = event.target.result;
insertContact(db, {
email: '[email protected]',
firstName: 'John',
lastName: 'Doe'
});
insertContact(db, {
email: '[email protected]',
firstName: 'Jane',
lastName: 'Doe'
});
};
Code language: JavaScript (javascript)
これで、ウェブブラウザでindex.html
ファイルを開くと、app.jsのコードが実行されて
- IndexedDBに
CRM
データベースを作成します。 CRM
データベースにContacts
オブジェクトストアを作成します。- オブジェクトストアに2つのレコードを挿入します。
ウェブブラウザでデベロッパーツールを開くと、Contacts
オブジェクトストアを含むCRM
データベースが表示されます。Contacts
オブジェクトストアには、次の図に示すようにデータが表示されます。

5) キーでオブジェクトストアからデータを読み取る
キーでオブジェクトを読み取るには、オブジェクトストアのget()
メソッドを使用します。次のgetContactById()
関数は、IDで連絡先を検索します。
function getContactById(db, id) {
const txn = db.transaction('Contacts', 'readonly');
const store = txn.objectStore('Contacts');
let query = store.get(id);
query.onsuccess = (event) => {
if (!event.target.result) {
console.log(`The contact with ${id} not found`);
} else {
console.table(event.target.result);
}
};
query.onerror = (event) => {
console.log(event.target.errorCode);
}
txn.oncomplete = function () {
db.close();
};
};
Code language: JavaScript (javascript)
オブジェクトストアのget()
メソッドを呼び出すと、非同期で実行されるクエリが返されます。
クエリは成功または失敗する可能性があるため、それぞれのケースを処理するためにonsuccess
とonerror
ハンドラーを割り当てる必要があります。
クエリが成功すると、event.target.resultに結果が取得されます。それ以外の場合は、event.target.errorCodeを介してエラーコードを取得します。
次のコードは、トランザクションが完了したらデータベースへの接続を閉じます。
txn.oncomplete = function () {
db.close();
};
Code language: JavaScript (javascript)
実際には、データベース接続は、すべてのトランザクションが完了したときにのみ閉じられます。
次のコードは、IDが1の連絡先を取得するために、onsuccess
イベントハンドラーでgetContactById()
を呼び出します。
request.onsuccess = (event) => {
const db = event.target.result;
getContactById(db, 1);
};
Code language: JavaScript (javascript)
出力

6) インデックスでオブジェクトストアからデータを読み取る
次に、メールインデックスを使用してデータをクエリするgetContactByEmail()
という新しい関数を定義します。
function getContactByEmail(db, email) {
const txn = db.transaction('Contacts', 'readonly');
const store = txn.objectStore('Contacts');
// get the index from the Object Store
const index = store.index('email');
// query by indexes
let query = index.get(email);
// return the result object on success
query.onsuccess = (event) => {
console.log(query.result); // result objects
};
query.onerror = (event) => {
console.log(event.target.errorCode);
}
// close the database connection
txn.oncomplete = function () {
db.close();
};
}
Code language: JavaScript (javascript)
動作方法。
- まず、
Contacts
オブジェクトストアからメールインデックスオブジェクトを取得します。 - 次に、
get()
メソッドを呼び出してインデックスを使用してデータを読み取ります。 - 第三に、クエリの
onsuccess
イベントハンドラーで結果を表示します。
以下は、onsuccess
イベントハンドラーでgetContactByEmail()
関数を使用する方法を示しています。
request.onsuccess = (event) => {
const db = event.target.result;
// get contact by email
getContactByEmail(db, '[email protected]');
};
Code language: JavaScript (javascript)
出力

7) オブジェクトストアからすべてのデータを読み取る
以下は、カーソルを使用してContacts
オブジェクトストアからすべてのオブジェクトを読み取る方法を示しています。
function getAllContacts(db) {
const txn = db.transaction('Contacts', "readonly");
const objectStore = txn.objectStore('Contacts');
objectStore.openCursor().onsuccess = (event) => {
let cursor = event.target.result;
if (cursor) {
let contact = cursor.value;
console.log(contact);
// continue next record
cursor.continue();
}
};
// close the database connection
txn.oncomplete = function () {
db.close();
};
}
Code language: JavaScript (javascript)
objectStore.openCursor()
は、オブジェクトストアを反復処理するために使用されるカーソルを返します。
カーソルを使用してオブジェクトストア内のオブジェクトを反復処理するには、onsuccess
ハンドラーを割り当てる必要があります。
objectStore.openCursor().onsuccess = (event) => {
//...
};
Code language: JavaScript (javascript)
event.target.result
はカーソルを返します。データを取得するには、cursor.value
プロパティを使用します。
cursor.continue()
メソッドは、オブジェクトストア内の次のレコードの位置にカーソルを進めます。
onsuccess
イベントハンドラ内で getAllContacts()
を呼び出すと、Contacts
オブジェクトストアからすべてのデータを表示できます。
request.onsuccess = (event) => {
const db = event.target.result;
// get all contacts
getAllContacts(db);
};
Code language: JavaScript (javascript)
出力

8) 連絡先の削除
オブジェクトストアからレコードを削除するには、オブジェクトストアの delete()
メソッドを使用します。
次の関数は、Contacts
オブジェクトストアから ID で連絡先を削除します。
function deleteContact(db, id) {
// create a new transaction
const txn = db.transaction('Contacts', 'readwrite');
// get the Contacts object store
const store = txn.objectStore('Contacts');
//
let query = store.delete(id);
// handle the success case
query.onsuccess = function (event) {
console.log(event);
};
// handle the error case
query.onerror = function (event) {
console.log(event.target.errorCode);
}
// close the database once the
// transaction completes
txn.oncomplete = function () {
db.close();
};
}
Code language: JavaScript (javascript)
そして、onsuccess
イベントハンドラで deleteContact()
関数を呼び出して、ID が 1 の連絡先を次のように削除できます。
request.onsuccess = (event) => {
const db = event.target.result;
deleteContact(db, 1);
};
Code language: JavaScript (javascript)
コードを実行すると、ID が 1 の連絡先が削除されていることがわかります。
すべてをまとめる
以下に、完全な app.js ファイルを示します。
(function () {
// check for IndexedDB support
if (!window.indexedDB) {
console.log(`Your browser doesn't support IndexedDB`);
return;
}
// open the CRM database with the version 1
const request = indexedDB.open('CRM', 1);
// create the Contacts object store and indexes
request.onupgradeneeded = (event) => {
let db = event.target.result;
// create the Contacts object store
// with auto-increment id
let store = db.createObjectStore('Contacts', {
autoIncrement: true
});
// create an index on the email property
let index = store.createIndex('email', 'email', {
unique: true
});
};
// handle the error event
request.onerror = (event) => {
console.error(`Database error: ${event.target.errorCode}`);
};
// handle the success event
request.onsuccess = (event) => {
const db = event.target.result;
// insert contacts
// insertContact(db, {
// email: '[email protected]',
// firstName: 'John',
// lastName: 'Doe'
// });
// insertContact(db, {
// email: '[email protected]',
// firstName: 'Jane',
// lastName: 'Doe'
// });
// get contact by id 1
// getContactById(db, 1);
// get contact by email
// getContactByEmail(db, '[email protected]');
// get all contacts
// getAllContacts(db);
deleteContact(db, 1);
};
function insertContact(db, contact) {
// create a new transaction
const txn = db.transaction('Contacts', 'readwrite');
// get the Contacts object store
const store = txn.objectStore('Contacts');
//
let query = store.put(contact);
// handle success case
query.onsuccess = function (event) {
console.log(event);
};
// handle the error case
query.onerror = function (event) {
console.log(event.target.errorCode);
}
// close the database once the
// transaction completes
txn.oncomplete = function () {
db.close();
};
}
function getContactById(db, id) {
const txn = db.transaction('Contacts', 'readonly');
const store = txn.objectStore('Contacts');
let query = store.get(id);
query.onsuccess = (event) => {
if (!event.target.result) {
console.log(`The contact with ${id} not found`);
} else {
console.table(event.target.result);
}
};
query.onerror = (event) => {
console.log(event.target.errorCode);
}
txn.oncomplete = function () {
db.close();
};
};
function getContactByEmail(db, email) {
const txn = db.transaction('Contacts', 'readonly');
const store = txn.objectStore('Contacts');
// get the index from the Object Store
const index = store.index('email');
// query by indexes
let query = index.get(email);
// return the result object on success
query.onsuccess = (event) => {
console.table(query.result); // result objects
};
query.onerror = (event) => {
console.log(event.target.errorCode);
}
// close the database connection
txn.oncomplete = function () {
db.close();
};
}
function getAllContacts(db) {
const txn = db.transaction('Contacts', "readonly");
const objectStore = txn.objectStore('Contacts');
objectStore.openCursor().onsuccess = (event) => {
let cursor = event.target.result;
if (cursor) {
let contact = cursor.value;
console.log(contact);
// continue next record
cursor.continue();
}
};
// close the database connection
txn.oncomplete = function () {
db.close();
};
}
function deleteContact(db, id) {
// create a new transaction
const txn = db.transaction('Contacts', 'readwrite');
// get the Contacts object store
const store = txn.objectStore('Contacts');
//
let query = store.delete(id);
// handle the success case
query.onsuccess = function (event) {
console.log(event);
};
// handle the error case
query.onerror = function (event) {
console.log(event.target.errorCode);
}
// close the database once the
// transaction completes
txn.oncomplete = function () {
db.close();
};
}
})();
Code language: JavaScript (javascript)
概要
- IndexedDB は、Web ブラウザに保存される大規模なオブジェクトストアです。
- IndexedDB はデータをキーと値のペアとして保存します。値は、単純なものから複雑なものまで、あらゆるデータにすることができます。
- IndexedDB は、1 つ以上のデータベースで構成されています。各データベースには、1 つ以上のオブジェクトストアが含まれています。通常、Web アプリケーションごとに IndexedDB にデータベースを作成します。
- IndexedDB は、永続的なインターネット接続を必要としないWebアプリケーション、特にオンラインとオフラインの両方で動作するアプリケーションに役立ちます。