Indexed Database API

Contents

  1. Introduction
  2. Connect
  3. Transactions
  4. Indices
  5. Cursor
  6. Performance
  7. Conclusion
  8. Resources

1. Introduction

Today many ways exist to store data inside a browser. What started with cookies has now evolved to very comprehensive storage mechanisms. Web Storage (LocalStorage/SessionStorage) is a key-value store which can hold up to 5MB of data. Web SQL brings SQL to the frontend and can store significantly more data. Although WebSQL is widely supported, the W3C dropped support because it was not a standard, but an implementation of SQLite.
The alternative for Web SQL is the Indexed Database API (IndexedDB), developed by the Mozilla foundation, a NoSQL database which stores key-values. The values can be complex structured objects and keys can be properties of those objects.

Below we will explore this next-gen storage mechanism where I will show how to persist this ‘customerData’ object

1
2
3
4
customerData=[
        {ssn:"444-44-4444",name:"Bill",age:35,email:"bill@company.com"},
        {ssn:"555-55-5555",name:"Donna",age:32,email:"donna@home.org"}
      ];

 

Please note that at this moment firefox is the only browser fully supporting all the specs of IndexedDB.

Connect

To setup a connection with an IndexedDB database you have to use the IndexedDB object. However, it is still an experimental feature and therefore hidden under browser prefixes. The following code snippet shows how to setup a connection and which events to listen to

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var indexedDB = window.indexedDB   || window.webkitIndexedDB ||          
                window.mozIndexedDB|| window.msIndexedDB ;

var dbconn = indexedDB.open("mydb",1) ; // 1 is the version number

dbconn.onerror = function(event) {  
  // Do something with dbconn.errorCode!  
};  
dbconn.onsuccess = function(event) {  
  // read and/or write to objectStores
}
dbconn.onupgradeneeded = function(event) {  
  // Update object stores and indices    
}  

Binding to these events is necessary, because this process is asynchronous. The onupgradeneeded event is special and is only fired when the database is created or the version number has changed. Inside this function you can create/modify the tables, which are called ‘object stores’. For the ‘customerData’ object we could create the following object store

1
2
3
4
5
var db = event.target.result ;

var objectStore = db.createObjectStore( "customers",{keyPath:"ssn"}) ;
objectStore.createIndex("name","name",{unique:false});         
objectStore.createIndex("email","email",{unique:true});

‘ssn’ and ’email’ should be unique, and with the keyPath option the ‘ssn’ value is used as the key. Indices are useful because they can be used for searches.

Transactions

Within a transaction you can read from, write to and delete entries from an object store. Furthermore a transaction has a scope: the objects stores it applies to, and a mode: read only or read-write. So, to write the customerData to the customers store do

1
2
3
4
5
6
var trans = event.target.result.transaction(['customers'], IDBTransaction.READ_WRITE) ;
                                
var objectStore = trans.objectStore('customers') ;
for( var i in customerData ) {
    objectStore.add(customerData[i]) ;
}

An entry lookup by its key value is equally simple

1
2
3
4
5
6
7
8
var trans = event.target.result.transaction(['customers'], IDBTransaction.READ) ;

var objectStore = trans.objectStore('customers') ;
var result = objectStore.get('444-44-4444') ; 
                                
result.onsuccess = function(e) {                                        
     alert("Name for SSN 444-44-4444 is " + r.result.name);      
}       

Instead of using the event.target.result you can use dbconn.result, which is useful when performing any of these actions outside the dbconn.onsuccess function.

1
var trans = dbconn.result.transaction(['customers'], IDBTransaction.READ) ;

 

Indices

Using an index is basically identical to a lookup by its key value. The only practical difference is that an index doesn’t have to be unique, in which case the entry with the lowest key value is returned

1
2
3
4
5
// Assume a transaction and object store object exist
var index = objectStore.index("name");  
index.get("Donna").onsuccess = function(event) {  
  alert("Donna's SSN is " + event.target.result.ssn);  
}; 

Cursors

With a cursor you can retrieve one or more entries. For example, if the above example would normally return multiple entries a cursor could be used as follows

1
2
3
4
5
6
7
8
var boundKeyRange = IDBKeyRange.only("Donna");
index.openCursor(boundKeyRange).onsuccess = function(event) {  
  var cursor = event.target.result;  
  if (cursor) {  
    alert("Name: " + cursor.key + ", SSN: " + cursor.value.ssn + ", email: " + cursor.value.email);   
    cursor.continue();  
  }  
};  

With the IDBKeyRange you can also define a lower and upper bound, which allows you to retrieve a range of entries

1
2
//Match anything between "Bill" and "Donna", but not including "Donna"  
var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);  

 

Performance

To get an idea about the performance I have written a performance test for WebSQL and IndexedDB. Testing with 500000 entries the only noticable difference is that IndexedDB is a lot faster with inserts. This is because it builds its indices afterwards. Only during this period the performance is not very good, but when this process is finished, it performs equally well as WebSQL.

Conclusion

IndexedDB is a very robust and well performing alternative to WebSQL, but unfortunately still experimental. The fact that Firefox and Internet Explorer have no plans to support WebSQL, will speed up the adoption of IndexedDB. 

Resources

Comments are closed.