Storing images and files in IndexedDB

The other day we wrote about how to Save images and files in localStorage, and it was about being pragmatic with what we have available today. There are, however, a number of performance implications with localStorage – something that we will cover on this blog later – and the desired future approach is utilizing IndexedDB. Here I’ll walk you through how to store images and files in IndexedDB and then present them through an ObjectURL.

The general approach

First, let’s talk about the steps we will go through to create an IndexedDB data base, save the file into it and then read it out and present in the page:

  1. Create or open a database.
  2. Create an objectStore (if it doesn’t already exist)
  3. Retrieve an image file as a blob
  4. Initiate a database transaction
  5. Save that blob into the database
  6. Read out that saved file and create an ObjectURL from it and set it as the src of an image element in the page

Creating the code

Let’s break down all parts of the code that we need to do this:

Create or open a database.


// IndexedDB
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB,
    IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction,
    dbVersion = 1;

/*
    Note: The recommended way to do this is assigning it to window.indexedDB,
    to avoid potential issues in the global scope when web browsers start
    removing prefixes in their implementations.

    You can assign it to a varible, like var indexedDB… but then you have
    to make sure that the code is contained within a function.
*/


// Create/open database
var request = indexedDB.open("elephantFiles", dbVersion);

request.onsuccess = function (event) {
console.log(“Success creating/accessing IndexedDB database”);
db = request.result;

db.onerror = function (event) {
console.log(“Error creating/accessing IndexedDB database”);
};

// Interim solution for Google Chrome to create an objectStore. Will be deprecated
if (db.setVersion) {
if (db.version != dbVersion) {
var setVersion = db.setVersion(dbVersion);
setVersion.onsuccess = function () {
createObjectStore(db);
getImageFile();
};
}
else {
getImageFile();
}
}
else {
getImageFile();
}
}

// For future use. Currently only in latest Firefox versions
request.onupgradeneeded = function (event) {
createObjectStore(event.target.result);
};

The intended way to use this is to have the onupgradeneeded event triggered when a database is created or gets a higher version number. This is currently only supported in Firefox, but will soon be in other web browsers. If the web browser doesn’t support this event, you can use the deprecated setVersion method and connect to its onsuccess event.

Create an objectStore (if it doesn’t already exist)


// Create an objectStore
console.log("Creating objectStore")
dataBase.createObjectStore("elephants");

Here you create an ObjectStore that you will store your data – or in our case, files – and once created you don’t need to recreate it, just update its contents.

Retrieve an image file as a blob


// Create XHR and BlobBuilder
var xhr = new XMLHttpRequest(),
    blob;

xhr.open("GET", "elephant.png", true);
// Set the responseType to blob
xhr.responseType = "blob";

xhr.addEventListener("load", function () {
    if (xhr.status === 200) {
        console.log("Image retrieved");

        // File as response
        blob = xhr.response;

        // Put the received blob into IndexedDB
        putElephantInDb(blob);
    }
}, false);
// Send XHR
xhr.send();

This code gets the contents of a file as a blob directly. Currently that’s only supported in Firefox.
Once you have received the entire file, you send the blob to the function to store it in the database.

Initiate a database transaction


// Open a transaction to the database
var transaction = db.transaction(["elephants"], IDBTransaction.READ_WRITE);

To start writing something to the database, you need to initiate a transaction with an objectStore name and the type of action you want to do – in this case read and write.

Save that blob into the database


// Put the blob into the dabase
transaction.objectStore("elephants").put(blob, "image");

Once the transaction is in place, you get a reference to the desired objectStore and then put your blob into it and give it a key.

Read out that saved file and create an ObjectURL from it and set it as the src of an image element in the page


// Retrieve the file that was just stored
transaction.objectStore("elephants").get("image").onsuccess = function (event) {
    var imgFile = event.target.result;
    console.log("Got elephant!" + imgFile);

    // Get window.URL object
    var URL = window.URL || window.webkitURL;

    // Create and revoke ObjectURL
    var imgURL = URL.createObjectURL(imgFile);

    // Set img src to ObjectURL
    var imgElephant = document.getElementById("elephant");
    imgElephant.setAttribute("src", imgURL);

    // Revoking ObjectURL
    URL.revokeObjectURL(imgURL);
};

Use the same transaction to get the image file you just stored, and then create an objectURL and set it to the src of an image in the page.
This could just as well, for instance, have been a JavaScript file that you attached to a script element, and then it would parse the JavaScript.

The complete code

So, here’s is the complete working code:


(function () {
    // IndexedDB
    var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB,
        IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction,
        dbVersion = 1.0;

    // Create/open database
    var request = indexedDB.open("elephantFiles", dbVersion),
        db,
        createObjectStore = function (dataBase) {
            // Create an objectStore
            console.log("Creating objectStore")
            dataBase.createObjectStore("elephants");
        },

        getImageFile = function () {
            // Create XHR and BlobBuilder
            var xhr = new XMLHttpRequest(),
                blob;

            xhr.open("GET", "elephant.png", true);
            // Set the responseType to blob
            xhr.responseType = "blob";

            xhr.addEventListener("load", function () {
                if (xhr.status === 200) {
                    console.log("Image retrieved");

                    // Blob as response
                    blob = xhr.response;

                    // Put the received blob into IndexedDB
                    putElephantInDb(blob);
                }
            }, false);
            // Send XHR
            xhr.send();
        },

        putElephantInDb = function (blob) {
            console.log("Putting elephants in IndexedDB");

            // Open a transaction to the database
            var transaction = db.transaction(["elephants"], IDBTransaction.READ_WRITE);

            // Put the blob into the dabase
            transaction.objectStore("elephants").put(blob, "image");

            // Retrieve the file that was just stored
            transaction.objectStore("elephants").get("image").onsuccess = function (event) {
                var imgFile = event.target.result;
                console.log("Got elephant!" + imgFile);

                // Get window.URL object
                var URL = window.URL || window.webkitURL;

                // Create and revoke ObjectURL
                var imgURL = URL.createObjectURL(imgFile);

                // Set img src to ObjectURL
                var imgElephant = document.getElementById("elephant");
                imgElephant.setAttribute("src", imgURL);

                // Revoking ObjectURL
                URL.revokeObjectURL(imgURL);
            };
        };

    request.onerror = function (event) {
        console.log("Error creating/accessing IndexedDB database");
    };

    request.onsuccess = function (event) {
        console.log("Success creating/accessing IndexedDB database");
        db = request.result;

        db.onerror = function (event) {
            console.log("Error creating/accessing IndexedDB database");
        };

        // Interim solution for Google Chrome to create an objectStore. Will be deprecated
        if (db.setVersion) {
            if (db.version != dbVersion) {
                var setVersion = db.setVersion(dbVersion);
                setVersion.onsuccess = function () {
                    createObjectStore(db);
                    getImageFile();
                };
            }
            else {
                getImageFile();
            }
        }
        else {
            getImageFile();
        }
    }

    // For future use. Currently only in latest Firefox versions
    request.onupgradeneeded = function (event) {
        createObjectStore(event.target.result);
    };
})();

Web browser support

IndexedDB
Supported since long (a number of versions back) in Firefox and Google Chrome. Planned to be in IE10 and a future version of Opera. Unclear about Safari.
onupgradeneeded
Supported in latest Firefox. Planned to be in Google Chrome soon and hopefully IE10 and a future version of Opera. Unclear about Safari.
Storing files in IndexedDB
Supported in Firefox 11 and later. Planned to be supported in Google Chrome. Hopefully IE10 will support it. Unclear about Safari and Opera.
XMLHttpRequest Level 2
Supported in Firefox and Google Chrome since long, Safari 5+ and planned to be in IE10 and Opera 12.
responseType “blob”
Currently only supported in Firefox. Will soon be in Google Chrome and is planned to be in IE10 and Opera 12. Unclear about Safari.

Demo and code

I’ve put together a demo with IndexedDB and saving images and files in it where you can see it all in action. Make sure to use any Developer Tool to Inspect Element on the image to see the value of its src attribute. Also make sure to check the console.log messages to follow the actions.

The code for storing files in IndexedDB is also available on GitHub, so go play now!

About Robert Nyman [Editor emeritus]

Technical Evangelist & Editor of Mozilla Hacks. Gives talks & blogs about HTML5, JavaScript & the Open Web. Robert is a strong believer in HTML5 and the Open Web and has been working since 1999 with Front End development for the web - in Sweden and in New York City. He regularly also blogs at http://robertnyman.com and loves to travel and meet people.

More articles by Robert Nyman [Editor emeritus]…


30 comments

  1. Florian

    Hello,

    Are you sure your demo is working on Firefox because on my side, it throw me an exception : “The object could not be cloned. ?

    This exception is also thrown in IE 10.

    This exception is thrown when you try to store a “complexe” object into the database.

    I was wondering that because I have the same issue on an application i am working on, and the only workaround I found is to store images in indexedDB as base64 string.

    Florian

    February 24th, 2012 at 02:25

  2. Robert Nyman

    Thanks for asking!
    Sorry, I should have been more clear about that: when it comes to storing files specifically, it works in Firefox 11 and later – please try Firefox Beta or Firefox Aurora and latest Google Chrome.

    I’ll update the blog post with that info.

    I hope that IE10 will support storing files before it is officially released.

    February 24th, 2012 at 02:47

  3. Andrea Giammarchi

    so now we talk Robert, good stuff!

    February 26th, 2012 at 13:54

    1. Robert Nyman

      Thank you. :-)

      February 26th, 2012 at 14:01

  4. Andrea Doimo

    I see the elephant iwth Chrome (19.0.1049.3 dev-m)
    Does this means that the demo worked fine? In the “Web browser support” section you state that Chrome doesn’t support some of the features listed.

    February 28th, 2012 at 08:20

    1. Robert Nyman

      Thanks for asking!
      There seems to be a case where Google Chrome intermediately stores the URL, which falsely gives the impression that it works (but without storing the actual file).

      I’ve changed the blog post and demo code to use responseType blob, that goes hand in hand with this, and you can see it in action in the updated demo.

      For more information on the Google Chrome status, please see the relevant bug.

      February 28th, 2012 at 08:51

  5. abral

    I’ve developed two demos that save files in an indexeddb for the december dev derby, eLibri and FileSystemDB.

    March 6th, 2012 at 15:46

    1. Robert Nyman

      Thanks for the tip!

      March 7th, 2012 at 01:28

      1. Ruben

        Hi Abral, I´ve seen your app eLibri, GREAT!, I´m trying to do something of the sort with a MS SQL server and in ASP, i´m getting it all right till the getting the Json file from the server and putting it into the IndexedDB database, could you explain how you do it and maybe give us a glance at the server file that delivers the Json file? (I know it is in PHP but it would help) Thank´s in advance.

        August 24th, 2012 at 08:46

  6. Odin Hørthe Omdal

    IDBTransaction.READ_WRITE is now called “readwrite”.

    I can tell you a bit about Opera:
    IndexedDB: I don’t think it’ll hit 12, but we’re working on it.
    XHR2 blob (and arraybuffer) is coming in Opera 12.
    onupgradeneeded: Of course we’re implementing the newest spec.

    March 14th, 2012 at 07:24

    1. Robert Nyman

      Thanks!
      I’ve updated the post with information on Opera.

      March 14th, 2012 at 07:31

  7. Víctor Quiroz

    Great Post! it’s the first that actually works. The only thing I would like to know is how to insert multiple rows using a loop?

    April 24th, 2012 at 10:49

    1. Víctor Quiroz

      nevermind, It worked perfectly like this:

      putElephantInDb = function (json) {
      console.log(“Putting elephants in IndexedDB”);

      // Open a transaction to the database

      $.each(json[0], function(i, w){
      var transaction = db.transaction([“elephant”], IDBTransaction.READ_WRITE);

      // Put the blob into the dabase
      var put = transaction.objectStore(“elephant”).put(w, “json”);

      // Retrieve the file that was just stored
      transaction.objectStore(“elephant”).get(w.id, “json”).onsuccess = function (event) {
      var mijson = event.target.result;
      console.log(mijson);
      };
      });
      };

      April 24th, 2012 at 10:59

      1. Robert Nyman

        And glad you found a solution!

        April 24th, 2012 at 15:05

    2. Robert Nyman

      Thanks, glad you liked it!

      April 24th, 2012 at 15:02

  8. Anonymous

    Is there a way to download huge content (~1 GB in total) to a database where the database needs to be in a consistent state all the time (e.g. a graph)?
    – Parsing the whole blob is impossible due to its size.
    – Using a single transaction for all chunks isn’t possible either, since during the download we need to get back to the event loop, and therefore the transaction becomes inactive.
    – Using temporary object stores in the *same* database would be possible, but doubles the space needed during download.

    May 20th, 2012 at 14:40

    1. Robert Nyman

      Good question! At this time, I’m not aware of doing it in any optimal way with IndexedDB. Like you say, temporary object stores might work but it’s not the desired solution.

      If you come up with something clever, please let us know!

      May 22nd, 2012 at 02:13

  9. Mitchell

    Hello Robert,
    Thank you for demo.

    Do you know why I get a security error if I load an image from my Web server?

    Security Error: Content at http://wheredidmybraingo.com/image.html may not load data from blob:ebac76f2-366c-c34d-af7c-fb1312f82999.

    Thank you, Mitchell

    July 20th, 2012 at 00:51

  10. Mitchell

    I fixed it :)

    I removed revokeObjectURL.

    Wish it was in documentation :(

    http://www.w3.org/TR/IndexedDB/

    Thank you again for demo, Mitchell

    July 20th, 2012 at 02:41

    1. Robert Nyman

      Interesting. Why would revokeObjectURL throw that error for you?

      July 31st, 2012 at 09:15

  11. Sam

    How much data can be stored on client side using indexedDB? 5MB, 50MB or there is no limit?

    August 27th, 2012 at 23:27

    1. Robert Nyman

      It is dependent on the web browser, but generally at least 50 MB.

      For Firefox, it’s 50 MB by default, and you can ask for more. More information in What is the storage limitation for “indexDB” in Firefox?

      August 28th, 2012 at 01:49

  12. Ruben

    Hi Robert,
    Thanks for sharing this example, I´m trying to get this to work but instead of a image I´m trying to retrieve a Json file from a web service and putting it to the IndexedDB objectStore.
    It looks something like this: [{“field1″:”value1”},{“field2″:”value2”},{“field3″:”value3”},{“field4″:”value4”}]
    but as I’m a newbie to JavaScript it’s beeing a nightmare…
    do you think you can post a example in how to do it?
    Thanks in advance.
    Ruben.

    September 25th, 2012 at 03:37

    1. Robert Nyman

      Can’t go into detail here, but if you have received JSON from the server, you can use the native JSON support in web browsers and then use the put method in the code above to save that into IndexedDB.

      September 25th, 2012 at 07:39

      1. Ruben

        Hi! thanks Robert, I’ve managed to do it, I’m only having a problem putting the records to the objectStore, I’m doing this:

        var result = eval( ‘(‘ + xhr.responseText + ‘)’ );
        for(i=0; i<result.length; i++){
        var transaction = db.transaction(["elephants"], IDBTransaction.READ_WRITE);
        var put = transaction.objectStore("elephants").put(result[i], "image");

        but it's actually putting something like this: [object Object],[object Object],[object Object],[object Object]" to the objectStore…
        I'm not sure if I have to use the eval() or even the loop, can you give me a hand please?

        September 25th, 2012 at 12:10

        1. Robert Nyman

          Please don’t use eval, use native JSON as linked in my previous comment above.

          Then, in put the first value should be the value and the second shouldn’t be “image”, but rather the name of your key (unless you want to store it all in the same key).

          September 26th, 2012 at 02:00

          1. Ruben

            I´ve just figured it out, I already have an object and dont need to do anything with it, just loop threw and put it to the objectStore.
            Thanks again Robert, and nice job on this code.
            Cheers.

            September 26th, 2012 at 11:45

          2. Robert Nyman

            Great! Good luck and thanks!

            September 26th, 2012 at 12:50

  13. Tom

    This works great for me in FF(19.02), but not in Chrome (25.0.1364.172 m) or IE (10.09.200…). The image src in the sample is pre set to “elephant.png”, so it gives the false impression that it works in those browsers as well, but I am getting an error: Uncaught Error: “DataCloneError: DOM IDBDatabase Exception 25” on this line: var put = transaction.objectStore(“elephants”).put(blob, “image”);

    Is it because blobs are not supported in those browsers?

    Thanks
    Tom

    March 17th, 2013 at 14:19

    1. Robert Nyman [Editor]

      Yes, I believe it could down to the blob support as responseType. The src of the img element should change to a blob reference, though, but I see what you mean.

      March 18th, 2013 at 02:53

Comments are closed for this article.