JavaScript IndexedDB

概要: このチュートリアルでは、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データベースはキーと値のペアを保存します

localStoragesessionStorageとは異なり、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.jphttps://javascripttutorial.dokyumento.jp
プロトコルhttpshttp
ポート44380

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.indexedDBopen()メソッドを使用します。

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()メソッドを呼び出すと、非同期で実行されるクエリが返されます。

クエリは成功または失敗する可能性があるため、それぞれのケースを処理するためにonsuccessonerrorハンドラーを割り当てる必要があります。

クエリが成功すると、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アプリケーション、特にオンラインとオフラインの両方で動作するアプリケーションに役立ちます。
このチュートリアルは役に立ちましたか?