Click here to Skip to main content
Click here to Skip to main content

Getting Started with IndexedDB

, 2 Aug 2012
Rate this:
Please Sign up or sign in to vote.
The article describes the HTML5 IndexedDB API and how to use it.

Introduction

One of the new JavaScript APIs that HTML5 has to offer is the IndexedDB API. In the past I wrote a post about the Web Storage API which is a simple key/value dictionary that is stored in the web browser and persists data. IndexedDB is a full blown index database which adds more offline capabilities to Web applications. In this article I’ll present the IndexedDB API and explain some basic concepts about it.

Background - What is IndexedDB?

IndexedDB API is a specification for an index database which exists in the browser. The IndexedDB is made of records holding simple values and hierarchical objects. Each of the records consists of a key path and a corresponding value which can be a simple type like string or date and more advance types like JavaScript objects and arrays. It can include indexes for faster retrieval of records and can store large amount of objects.

IndexedDB has two API modes – synchronous and asynchronous. Most of the time you will use the asynchronous API. The synchronous API was created to be used only with conjunction with Web Workers (and it is currently isn’t supported by most of the browsers).

The IndexedDB API is exposed through the window.indexedDB object. The API isn’t fully supported by most of the browsers today. The major browsers that support the API expose the indexedDB object with their prefixes. The following line of code should be used before you use the indexedDB currently and you should use libraries like Modernizr to detect if the browser supports the API:

var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;

In the day of writing the post, IndexedDB is supported by Firefox from version 4 (Firefox is currently the most updated browser with regard to the specifications), Chrome from version 11 and IE10.

Using the IndexedDB API, you can take Web applications offline and decrease the number of server round-trips since you can store common data in a local database instead of the server database.

Opening a Database

Before you can start using the IndexedDB you first need to open the database for use. Since the IndexedDB is working asynchronous calling the open function will return an IDBRequest object which you will use to wire a success and error event handlers. Here is an example of opening a database:

var db;
var request = indexedDB.open("TestDatabase");
request.onerror = function(evt) {
  console.log("Database error code: " + evt.target.errorCode);
};
request.onsuccess = function(evt) {
  db = request.result;
};

In the example, a call to the open function is used to open a database with the name TestDatabase. After the call, two callback functions are wired to the returned IDBRequest one for the onerror and one for the onsuccess. In the success callback you can get the database object from the request and store it for further use.

The open function accepts another parameter which isn’t passed in the example which is the version number of the database. The version number is used to change the version of the database. In the case where the database’s version is smaller then the provided version the upgradeneeded event will be fired and you will be able to change the database’s structure in it’s handler. Changing the version of the database is the only way to change the structure of the database.

Creating an objectStore

The IndexedDB can hold one or more objectStores. objectStores resemble tables in relational databases but are very different from them. They hold the key/value records and can have key paths, key generators and indexes. You use the IndexedDB’s createObjectStore function to create an objectStore. The function gets a name for the objectStore and an options object to configure things like key paths and key generators.

Key paths and key generators are used to create the main index for the stored value. The key path is a string that defines how to extract a key from the given value. It is used with JavaScript objects which have the a property with the exact name of the key path. If a property with the exact name doesn’t exists you need to supply a key generator such as autoIncrement. The key generator is used to hold any kind of value. It will generate a key automatically for you but you can also pass your own key for a stored value if you want.

objectStores can also have indexes which will be used later for data retrieval. Indexes are created with the objectStore createIndex function which can get three parameters – the name of the index, the name of the property to put the index on and an options object.

Here is an example of creating an objectStore inside the onupdateneeded event handler:

var peopleData = [
    { name: "John Dow", email: "john@company.com" },
    { name: "Don Dow", email: "don@company.com" }
];
 
function initDb() {
    var request = indexedDB.open("PeopleDB", 1);  
    request.onsuccess = function (evt) {
        db = request.result;                                                            
    };
 
    request.onerror = function (evt) {
        console.log("IndexedDB error: " + evt.target.errorCode);
    };
 
    request.onupgradeneeded = function (evt) {                   
        var objectStore = evt.currentTarget.result.createObjectStore("people", 
                                     { keyPath: "id", autoIncrement: true });
 
        objectStore.createIndex("name", "name", { unique: false });
        objectStore.createIndex("email", "email", { unique: true });
 
        for (i in peopleData) {
            objectStore.add(peopleData[i]);
        }
    };
}

The example show some important things:

  • onupdateneeded is called before the onsuccess callback and therefore you can use the evt.currentTarget.result to get the database which is getting opened.
  • The key path is created with an id string which doesn’t exists in the supplied object. The key path is used with conjunction with the autoIncrement option to create an incrementing key generator.
  • You can use the unique constraint on indexes in order to enforce simple constraints. When the unique option is true, the index will enforce the constraint for inserted emails.
  • You can use the objectStore’s add function to add records to the objectStore.

Creating a Transaction

When you have an objectStore you will probably want to use it with CRUD (create/read/update/delete) operations. The only way to use CRUD in IndexedDB is through an IDBTransaction object. The IDBTransaction is also supported with browser prefixes currently (like the IndexedDB object), so the following line of code should be used:

var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;

The IDBTransaction object can be created in three modes: read-only, read/write and snapshot. You should use the read/write mode only when you want to update the objectStores and read-only in other cases. The reason for that is that read-only transaction can run concurrently. By default transactions run in read-only mode.

The transactions are asynchronous as all the other IndexedDB API calls. That means that you can wire handlers for their error, abort, and complete events. Here is an example of opening an add transaction:

var transaction = db.transaction("people", IDBTransaction.READ_WRITE);
var objectStore = transaction.objectStore("people");                    
var request = objectStore.add({ name: name, email: email });
request.onsuccess = function (evt) {
    // do something when the add succeeded                          
};

The example shows that you first create a transaction object for the people objectStore. Then, you retrieve the objectStore from the transaction object and perform an operation on it. That operation is called asynchronous and therefore you wire an onsuccees event handler to deal with the request’s success. In the example I didn’t wire any of the transaction event handlers but you can use them like in the following example:

transaction.oncomplete = function(evt) {  
  // do something after the transaction completed  
};

Retrieving Data

In order to retrieve data from the objectStore you will use a transaction object and also the objectStore’s get function. The get function expects a value which will be used against the key path of the objectStore. Here is an example of using the get function:

var transaction = db.transaction("people");  
var objectStore = transaction.objectStore("people");  
var request = objectStore.get(1);  
request.onsuccess = function(evt) {  
  alert("Name for id 1 " + request.result.name);  
};

Another way to retrieve data is using a cursor. You will use cursors when the key path isn’t known to you. Cursors are opened against an obejctStore which is part of a transaction. Here is an example of using a cursor:

var transaction = db.transaction("people", IDBTransaction.READ_WRITE);
var objectStore = transaction.objectStore("people");
 
var request = objectStore.openCursor();
request.onsuccess = function(evt) {  
    var cursor = evt.target.result;  
    if (cursor) {  
        output.textContent += "id: " + cursor.key + " is " + cursor.value.name + " ";                            
        cursor.continue();  
    }  
    else {  
        console.log("No more entries!");  
    }  
};

In the example the openCursor function is called against the objectStore. Then an onsuccess function is wired to the cursor request and is used to write to a div called output the data which was retrieved by the cursor. The previous example is a very simple cursor example. Cursors can be used with more sophisticated queries which won’t be shown in this post.

The Full Example

Here is a full example of some IndexedDB Concepts:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>IndexedDB</title>
    <script type="text/javascript">
        var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
        var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
        var db;
        (function () {     
            var peopleData = [
                { name: "John Dow", email: "john@company.com" },
                { name: "Don Dow", email: "don@company.com" }
            ];
 
            function initDb() {
                var request = indexedDB.open("PeopleDB", 1);  
                request.onsuccess = function (evt) {
                    db = request.result;                                                            
                };
 
                request.onerror = function (evt) {
                    console.log("IndexedDB error: " + evt.target.errorCode);
                };
 
                request.onupgradeneeded = function (evt) {                   
                    var objectStore = evt.currentTarget.result.createObjectStore(
                             "people", { keyPath: "id", autoIncrement: true });
 
                    objectStore.createIndex("name", "name", { unique: false });
                    objectStore.createIndex("email", "email", { unique: true });
 
                    for (i in peopleData) {
                        objectStore.add(peopleData[i]);
                    }
                };
            }
 
            function contentLoaded() {
                initDb();                
                var btnAdd = document.getElementById("btnAdd");
                var btnDelete = document.getElementById("btnDelete");
                var btnPrint = document.getElementById("btnPrint");                
 
                btnAdd.addEventListener("click", function () {
                    var name = document.getElementById("txtName").value;
                    var email = document.getElementById("txtEmail").value;
 
                    var transaction = db.transaction("people", IDBTransaction.READ_WRITE);
                    var objectStore = transaction.objectStore("people");                    
                    var request = objectStore.add({ name: name, email: email });
                    request.onsuccess = function (evt) {
                        // do something after the add succeeded
                    };
                }, false);
 
                btnDelete.addEventListener("click", function () {
                    var id = document.getElementById("txtID").value;
 
                    var transaction = db.transaction("people", IDBTransaction.READ_WRITE);
                    var objectStore = transaction.objectStore("people");
                    var request = objectStore.delete(id);
                    request.onsuccess = function(evt) {  
                        // It's gone!  
                    };
                }, false);
 
                btnPrint.addEventListener("click", function () {
                    var output = document.getElementById("printOutput");
                    output.textContent = "";
 
                    var transaction = db.transaction("people", IDBTransaction.READ_WRITE);
                    var objectStore = transaction.objectStore("people");
 
                    var request = objectStore.openCursor();
                    request.onsuccess = function(evt) {  
                        var cursor = evt.target.result;  
                        if (cursor) {  
                            output.textContent += "id: " + cursor.key + 
                                        " is " + cursor.value.name + " ";                            
                            cursor.continue();  
                        }  
                        else {  
                            console.log("No more entries!");  
                        }  
                    };  
                }, false);              
            }
 
            window.addEventListener("DOMContentLoaded", contentLoaded, false); 
    })();       
    </script>
</head>
<body>
    <div id="container">
        <label for="txtName">
            Name:
        </label>
        <input type="text" id="txtName" name="txtName" />
        <br />
        <label for="txtEmail">
            Email:
        </label>
        <input type="email" id="txtEmail" name="txtEmail" />
        <br />
        <input type="button" id="btnAdd" value="Add Record" />
        <br />
        <label for="txtID">
            ID:
        </label>
        <input type="text" id="txtID" name="txtID" />
        <br />
        <input type="button" id="btnDelete" value="Delete Record" />
        <br />
        <input type="button" id="btnPrint" value="Print objectStore" />
        <br />
        <output id="printOutput">
        </output>
    </div>
</body>
</html>
Pay attention – this example will only work on Firefox 10 since Firefox 10 is currently the only browser that updated the IndexedDB API implementation to use the latest specification version.

IndexedDB and Web Storage APIs

As written in the introduction, there are two kinds of data stores in the browsers – the IndexedDB and the Web Storage. One of the questions that I hear a lot is why to have two different storage types? In simple scenarios where a key/value pairs are needed with very small amount of data, the Web Storage is much more suitable then IndexedDB and can simplify your work. On the other hand, in scenarios where you need efficient search for values or you have large number of objects that you want to store on the client-side, IndexedDB is preferable. Both of the options complement each other and can be used together in the same application.

Summary

IndexedDB includes a massive API for using a built-in index database in the browser. It can be used to store data on the client-side and with Web Storage to offer to opportunity to take applications offline and to reduce server round-trips for data retrieval. For further information about IndexedDB you can go to its specifications in this link.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Gil Fink
Architect Sela Group
Israel Israel
Gil Fink is an expert in web development and Microsoft web and data platforms. He works as a senior consultant and architect at Sela Group. He is currently consulting for various enterprises and companies, where he helps to develop Web and RIA-based solutions. He conducts lectures and workshops for individuals and enterprises who want to specialize in infrastructure and web development. He is also a co-author of several Microsoft Official Courses and training kits and the founder of Front-End.IL meetup. You can read his publications at his website: http://www.gilfink.net
Follow on   Twitter   Google+

Comments and Discussions

 
GeneralMy vote of 5 PinmemberRaul Arturo Medina Nussbaum17-Jul-14 7:51 
GeneralIndexedDB on Firefox, Chrome, Safari, IE, Opera, Android, iOS and packaged Chrome Apps. PinmemberRaul Arturo Medina Nussbaum17-Jul-14 7:49 
Questionupdation and deletion is not working in latest version of firefox and chrome PinmemberNarendra Dutta16-Feb-14 4:40 
QuestionExcellent post PinmemberSankarHarsha7-Jan-14 19:53 
Questionlatest IndexedDB Pinmemberraju dasa19-Dec-12 3:05 
QuestionExample not working in Chrome 21.0.1180.83 m or Firefox 15.0 PinmemberNeville Franks28-Aug-12 21:19 
AnswerRe: Example not working in Chrome 21.0.1180.83 m or Firefox 15.0 Pinmemberjab_packerfan18-Dec-12 13:33 
QuestionRegarding data security PinmemberNIRAL SONI3-Aug-12 1:33 
AnswerRe: Regarding data security PinmemberGil Fink3-Aug-12 2:11 
QuestionExporting Data from the Indexed DB Pinmembergaurish thakkar2-Aug-12 17:53 
AnswerRe: Exporting Data from the Indexed DB PinmemberGil Fink3-Aug-12 1:20 
GeneralRe: Exporting Data from the Indexed DB Pinmembergaurish thakkar6-Aug-12 0:05 
GeneralMy vote of 5 PinmemberFlorian Rappl19-Mar-12 11:03 
GeneralRe: My vote of 5 PinmemberGil Fink19-Mar-12 20:59 
GeneralMy vote of 5 Pinmemberhaviay15-Mar-12 2:29 
Questionintergrate with jquery Pinmembervicola3613-Mar-12 22:20 
AnswerRe: intergrate with jquery PinmemberGil Fink16-Mar-12 3:07 
GeneralMy vote of 5 PinmvpShai Raiten8-Mar-12 6:22 
GeneralRe: My vote of 5 PinmemberGil Fink13-Mar-12 22:14 
Questioncreate new Index Pinmemberagha_ali2218-Feb-12 8:50 
AnswerRe: create new Index PinmemberGil Fink22-Feb-12 20:00 
GeneralRe: create new Index Pinmemberagha_ali2223-Feb-12 17:30 
GeneralRe: create new Index PinmemberGil Fink24-Feb-12 1:18 
GeneralMy vote of 5 Pinmemberdefwebserver16-Feb-12 5:32 
GeneralRe: My vote of 5 PinmemberGil Fink18-Feb-12 1:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 2 Aug 2012
Article Copyright 2012 by Gil Fink
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid