Quick Tip: Store Data in the Browser with IndexedDB

The IndexedDB API gives you a fast key/value store in the browser. And it is supported in more browsers than you think (thanks to a shim, it can work in even older ones). This means that when the need arises, you have a way to store a persistent collection of data in the user’s browser, without having to depend on internet connectivity. The bad news is that IndexedDB has a very cumbersome and verbose API that is a pain to use. Luckily, there is a tiny library that can be of huge help. It is called db.js and in this quick tip I will show you how to use it.

How to use db.js

IndexedDB relies heavily on setting up callbacks, listening for errors, and working with lots of temporary variables. Db.js hides this from you and exposes a more linear and easy to work interface that any jQuery fan would appreciate. The library even uses the same deferred/promise mechanism as jQuery. If you don’t know what any of the previous things I mentioned mean, don’t worry, it will become clearer once you see some code.

For this purpose, we are going to build a small demo application. It is going to do the following:

  • First, define the schema of a new key/value store, along with a version number;
  • Attempt to open the database, and if everything went fine proceed;
  • Set up an event listener for clicks on the “Add” element. In the body of the listener we will insert a record to the database and display the item on the page;
  • Lastly, when an item is clicked, delete the appropriate record from the database and remove it from the page.

In addition to being super fast, these operations are also persistent, so that when you refresh the page or close your browser, the data will still be there. In a real application you would want to sync the contents of the IndexedDB store to your server, but we will not be doing this today.

IndexedDB App

IndexedDB App

The Code

The first step is to define the schema of our data store. Unlike relational databases like MySQL, we don’t have the notion of tables with predefined columns and types here. IndexedDB can hold arbitrary JavaScript objects within a single data store. The only requirement in this step is to optionally choose an id field, whether you want it to auto-increment, and to define zero or more indexes.

assets/js/script.js

// Use the db.js library, define a schema,
// and listen for events

db.open({
    name: 'database',
    version: 2,
    schema: {
        items: {
            key: {
                keyPath: 'id',
                autoIncrement: true
            },
            indexes: {
                color: { unique: false }
            }
        }
    }
})

Defining an index will tell IndexedDB that it should look for that property in objects you insert in the data store. You will then be able to retrieve and sort all objects that have this index. In the example above I am defining an index on the color property and telling db.js that I don’t want it to be unique (if it were, I would be able to store only one object with that color). I am not using this index directly in the application, but I decided to include it anyway to show you how it’s done. If you wish to update the schema, you will need to increment the version number as well.

As with everything else, opening a database in IndexedDB is asynchronous and might fail in a number of cases (the browser doesn’t support it, the version number is wrong, or the user is in incognito mode). We have to pass a callback to the done() method after opening the database, to be sure everything was successful  We will also get a reference to the server object which we need to run queries.

db.open({
    name: 'database',
    version: 2,
    schema: {
        items: {
            key: {
                keyPath: 'id',
                autoIncrement: true
            },
            indexes: {
                color: { unique: false }
            }
        }
    }
}).done(function(server){

    // The database was successfully opened. We can
    // run transactions using the server varaible

    // Listen for the document ready event, as we will
    // be working with the dom

    $(function() {

        // Cache some selectors

        var add = $('#add'),
            items = $('#items');

        var colors = ['blue', 'green', 'yellow', 'pink'];

        // On dom.ready, select all items and update the #items ul
        server.items.query().filter().execute().done(function(results){

            if(!results){
                return;
            }

            $.each(results, function(){
                createItem(this);
            });

        });

        // Listen for clicks on the add button
        add.click(function(){

            var item = {
                text: (new Date()).toTimeString(),
                color: colors[ Math.floor( Math.random()*colors.length )]
            };

            server.items.add(item).done(function(){
                createItem(item);
            });

            // If you wish to update an item:
            // server.items.update({id:123, color:'pink', text:'asdf'});
        });

        // When an item is clicked, remove it from the database.
        $('#items').on('click', 'li:not(#add)', function(){
            var item = $(this);
            server.items.remove( item.data('id') ).done(function(){
                item.fadeOut();
            });
        });

        function createItem(item){
            var tmp = $('<li><p></p></li>');

            tmp.addClass( item.color )
                .data('id', item.id)
                .find('p').text( item.text );

            items.prepend(tmp);
        }

    });

}).fail(function(error){

    console.error("An error occured: ", error);

});

Inside the callback, we:

  • listen for the document.ready event;
  • select all existing items from the database and display them on the page;
  • listen for clicks on the “Add” element and create new items;
  • listen for clicks on the items themselves and remove them from the database;
  • define a callback function for creating items and appending them to the page.

In the fail event, we only log the encountered error to the console. With this our simple IndexedDB example is complete!

For more information and examples on using db.js, consult this blog post by the developer, or read the source code.

Join our newsletter and get our PSDs!19,489 people learn about HTML5, JS and more. Join them!

by Martin Angelov

Martin is a web developer with an eye for design from Bulgaria. He founded Tutorialzine in 2009 and it still is his favorite side project.

5 Comments

  1. Alpha says:

    This article doesn't appears in the home page of your blog. It's very interesant !

    Tnaks Martin for sharing.

  2. Alpha says:

    This article doesn't appears in the home page of your blog. It's very interesting !

    Tanks Martin for sharing.

  3. Amin says:

    Awesome. All other tutorials don't work. Thank you!

  4. Murali says:

    Martin,
    this is a good one. thanks for the tutorialzine.
    However, writing CRUD for every db seems to be cumbersome.. I found taffydb (http://www.taffydb.com/) to be easier to incorporate. Taffydb is easier for smaller operations while pouchdb was good for search type databases

  5. JS says:

    FYI: https://github.com/facebook/IndexedDB-polyfill is another nice shim

Add Comment

Add a Reply

HTML is escaped automatically. Surround code blocks with <pre></pre> for readability.
Perks:   **bold**   __italics__   [some text](http://example.com) for links