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

Getting Started with IndexedDB

By , 2 Aug 2012
 

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
Member
Gil Fink is an expert in ASP.NET and Microsoft data platform and serves as a Senior Architect at SELA Group. He is a Microsoft data platform MVP and a certified MCPD Enterprise Application Developer. Gil has worked in the past in variety of positions and projects as a leading developer, team leader, consultant and more. His interests include Entity Framework, Enterprise Library, WCF, LINQ, ADO.NET and many other new technologies from Microsoft.
 

My technical blog: http://www.gilfink.net

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionlatest IndexedDBmemberraju dasa19 Dec '12 - 3:05 
I too created a js lib file for IndexedDB, tested with FF17,CH23.
Might help someone in need, check my blog post: IndexedDB Updated[^]
QuestionExample not working in Chrome 21.0.1180.83 m or Firefox 15.0memberNeville Franks28 Aug '12 - 21:19 
It looks like parts of the IndexedDB API have changed since this code was written and it now gives errors on:
db.transaction("people", IDBTransaction.READ_WRITE);
in Chrome.
 
I did try changing this to:
db.transaction(["people"], 'readwrite');
but still had problems.
 
The HTML5Rocks ToDO example http://www.html5rocks.com/en/tutorials/indexeddb/todo/[^] has been updated at: http://pastebin.com/nnrNkk8T[^] with the required changes.
 
Firefox 15.0 doesn't know about window.mozIndexedDB, however I suspect this is because I'm executing the html file on the local file system as in file:///
Neville Franks, Author of Surfulater www.surfulater.com "Save what you Surf" and ED for Windows www.getsoft.com
 

AnswerRe: Example not working in Chrome 21.0.1180.83 m or Firefox 15.0memberjab_packerfan18 Dec '12 - 13:33 
I got this to work looking at the code at the link mentioned. Thanks.
jab

QuestionRegarding data securitymemberNIRAL SONI3 Aug '12 - 1:33 
First of all, thanks for sharing such a nice article on new concept of data store at client side.
 
What I would like to understand is how to maintain the data security? Because we are storing the data locally, can someone simply access this data and play with it ? What is the best way to protect our client side data (for IndexedDB as well as for Local storage)?
 
(Also, tt is obvious that we should not store any important data at client side)
Thanks & Regards,
Niral Soni

AnswerRe: Regarding data securitymemberGil Fink3 Aug '12 - 2:11 
Both IndexedDB and Web Storage APIs work per domain.
That means that every domain will have a different IndexedDB/Web Storage for its disposal and different domains can't tempare the storages of other domains.
Regaridng the question of security, as you wrote, you shouldn't save important data or secured data in those storages.
The storages can be tempared by someone who knows were they are stored by the browser and it is the responsibilty of the browser vendors to persist the data in a way that they won't be tempared easily.
QuestionExporting Data from the Indexed DBmembergaurish thakkar2 Aug '12 - 17:53 
Can i take the backup of the data stored in the indexed DB by any means?
AnswerRe: Exporting Data from the Indexed DBmemberGil Fink3 Aug '12 - 1:20 
Since IndexedDB is a client-side storage it doesn't support backups.
GeneralRe: Exporting Data from the Indexed DBmembergaurish thakkar6 Aug '12 - 0:05 
So there is no way i can save the local data from the storage to a format which i can take out from the browser.
 
I m working on a local website offline which requires saving of the user data.
But i am unable to do as i m not using any server-client model
GeneralMy vote of 5memberFlorian Rappl19 Mar '12 - 11:03 
Very good article! Just to add a personal opinion about IndexedDB:
I thought (the now deprecated) WebSQL was quite cool, also since it provided an easy to use API. IndexedDB has the huge advantage of being completely async, however, it requires a lot more coding and is far more complex...
Let's see where this is going!
 
Thanks again for your nice article.
GeneralRe: My vote of 5memberGil Fink19 Mar '12 - 20:59 
Hi Florian,
 
First of all, thanks.
Regarding what you wrote, IndexedDB is complex when comparing to WebSQL API.
As someone that used and using SQL, I feel much comfortable to use it right now (maybe it will change in the future Wink | ;-) ).
On the other hand, IndexedDB offers a lot of flexibility and of course the async interface which make it more suitable for client-side development. As you wrote, time will tell if the IndexedDB API will be a successful API or not.
 
Best Regards,
Gil
GeneralMy vote of 5memberhaviay15 Mar '12 - 2:29 
Good stuff
Questionintergrate with jquerymembervicola3613 Mar '12 - 22:20 
how would you intergrate with jquery/ajax especially the key value pairs below
 
var peopleData = [
               { name: "John Dow", email: "john@company.com" },
               { name: "Don Dow", email: "don@company.com" }
           ];
vicola3six

AnswerRe: intergrate with jquerymemberGil Fink16 Mar '12 - 3:07 
IndexedDB is a client-side JavaScript API.
That means that it is not connected to Ajax or to XmlHttpRequest.
On the other hand, you can combine the IndexedDB API with jQuery/Ajax to create better user experience.
For example, use IndexedDB in offline web application scenarios and when you get back online update a backend server using Ajax.
GeneralMy vote of 5mvpShai Raiten8 Mar '12 - 6:22 
Great article!!!
GeneralRe: My vote of 5memberGil Fink13 Mar '12 - 22:14 
Thanks!
Questioncreate new Indexmemberagha_ali2218 Feb '12 - 8:50 
suppose we have another column city in peopleData and also want to create an index of city how can i do this e.g (just like adding a column in a table).
I changed the version of db and in onupgradeneeded what chages do i need because object store is already created
AnswerRe: create new IndexmemberGil Fink22 Feb '12 - 20:00 
Hi,
 
Sorry for the late answer.
Here is an example of how to achieve what you wrote:
request.onupgradeneeded = function (evt) {                                           
   var objectStore = evt.currentTarget.transaction.objectStore("people");
   objectStore.createIndex("city", "city", { unique: false });                        
 
   // Store a new person in the objectStore. 
   var req = objectStore.add({ name: "New person", email: "newperson@company.com", city: "New York" });
   req.onsuccess = function (event) {
      // The add succeeded  
   };
};
 
Best Regards,
Gil
GeneralRe: create new Indexmemberagha_ali2223 Feb '12 - 17:30 
thanks and sorry if i disturbed you
GeneralRe: create new IndexmemberGil Fink24 Feb '12 - 1:18 
You didn't disturbe me at all Smile | :)
GeneralMy vote of 5memberdefwebserver16 Feb '12 - 5:32 
You are a good writer.
GeneralRe: My vote of 5memberGil Fink18 Feb '12 - 1:09 
10X
Questionindexed dbmemberagha_ali2213 Feb '12 - 1:07 
is there any specific reason why you used (function () { .................})(); and window.addEventListener("DOMContentLoaded", contentLoaded, false);
AnswerRe: indexed dbmemberGil Fink13 Feb '12 - 1:34 
Hi agha_ali22,
 
First regarding the immediate function ((function () { .................})(); ), I don't want to pollute the global JavaScript scope with functions and therefore I use this behavior.
Regarding the DOMContentLoaded, this is an event that is raised after the DOM was loaded and before the page's load event. The DOMContentLoaded is the first event that you can wire to start manipulating the DOM. It also gives you guarantee that all the HTML document is available for manipulation.
 
Best Regards,
Gil
GeneralRe: indexed dbmemberagha_ali2213 Feb '12 - 1:50 
thanks and excellent article for beginner, can you suggest where i can learn more about indexed db in depth
GeneralRe: indexed dbmemberGil Fink13 Feb '12 - 2:01 
Hi,
 
One place to get updates about the changes in the IndexedDB specifications is the specifictions themsleves: http://www.w3.org/TR/IndexedDB/[^]
Another place is Mozilla's MDN: https://developer.mozilla.org/en/IndexedDB[^]
 
Best Regards,
Gil
GeneralRe: indexed dbmemberagha_ali2213 Feb '12 - 2:39 
thanks
QuestionHow do you sync it back when online?memberPungiBajao6 Feb '12 - 3:43 
Could you shed some light on how you might sync the database when back online? Does that mean I have store the main copy on my webserver or a share?
AnswerRe: How do you sync it back when online?memberGil Fink6 Feb '12 - 3:57 
The IndexedDB API doesn't include sync API.
If you want to sync it back to the server-side, you will have to implement that functionality using Ajax (using XMLHttpRequest, jQuery or your favorite JavaScript library).
You can (and probably should) store the main copy of the database on your web server but you can download public and common data to IndexedDB to decrease the amount of server round-trip and to support offline scenarios.
GeneralMy vote of 5memberAbinash Bishoyi5 Feb '12 - 6:11 
Great
QuestionGood Job Gilmemberaamironline5 Feb '12 - 3:13 
Simple article to get started....
Mohamed Aamir Maniar is a software enthusiastic and founder of Maniar Technologies Pvt Ltd.

These days he keeps himself busy in improving his technology update website www.technobits.net - Latest buzz tips and tricks in Software development.

AnswerRe: Good Job GilmemberGil Fink5 Feb '12 - 3:14 
10X Mohamed
GeneralRe: Good Job Gilmemberaamironline5 Feb '12 - 18:16 
Would love to invite you to join my Technology Group at Facebook at http://www.facebook.com/groups/mail.techgroup/[^].
Mohamed Aamir Maniar is a software enthusiastic and founder of Maniar Technologies Pvt Ltd.

These days he keeps himself busy in improving his technology update website www.technobits.net - Latest buzz tips and tricks in Software development.

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 2 Aug 2012
Article Copyright 2012 by Gil Fink
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid