DEV Community

Cover image for Make websites work offline - Offline Storage. Making IndexedDB the Hero!
Saurabh Daware ๐ŸŒป
Saurabh Daware ๐ŸŒป

Posted on

Make websites work offline - Offline Storage. Making IndexedDB the Hero!

Note: This article does not expect you to know anything from Part 1.

Traditionally cookies were used for storing local data. But with HTML5 APIs, we got new options like localStorage, sessionStorage, WebSQL, and IndexedDB. In this article, we will specifically talk about IndexedDB.

Let's say you got Service Workers setup done and now your website loads offline. But... what if you want to store and retrieve a particular data? you cannot just fetch() from your API since the user is offline.

In this case, you can store data in IndexedDB!

The Indexed Database API is a JavaScript application programming interface provided by web browsers for managing a NoSQL database of JSON objects. It is a standard maintained by the World Wide Web Consortium.

~ Wikipedia

IndexedDB is provided by the browser and thus does not need internet for performing CRUD (Create Read Update Delete) operations. It is something like SQLite in Android (minus the SQL).

Implementation

If you prefer learning yourself from codesandbox, you can checkout IndexedDB Example.

For the browsers that use prefix, we can start with something like

window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction || {READ_WRITE: "readwrite"};
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;

if (!window.indexedDB) {
    console.log("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
}
Enter fullscreen mode Exit fullscreen mode

Before we go to the next code, let me warn you about something: IndexedDB is not promisified and thus is largely dependent on onsuccess and onerror callbacks. There are libraries like idb that provide promisified version of IndexedDB but for this article I will stick to the vanilla IndexedDB code.

Open/Create Database

Opening a database automatically creates new database if it doesn't exist

let db;
const request = indexedDB.open("MyTestDatabase");
request.onsuccess = function(event) {
    db = event.target.result;
};

Enter fullscreen mode Exit fullscreen mode

> Defining schema/values

When you create a new database, the onupgradeneeded event will be triggered. We can create objectStores here,

request.onupgradeneeded = function() {
    const db = event.target.result;
    const userObjectStore = db.createObjectStore("users", {keyPath: "userid"});
    userObjectStore.createIndex("name", "name", { unique: false });
    userObjectStore.createIndex("email", "email", { unique: true });
}
Enter fullscreen mode Exit fullscreen mode

Thus, the complete code to create/open a database would look something like:

async function openDatabase() {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open("MyTestDatabase");
        request.onsuccess = function(event) {
            resolve(event.target.result);
        }

        request.onupgradeneeded = function() {
            const db = event.target.result;
            const userObjectStore = db.createObjectStore("users", {keyPath: "userid"});
            userObjectStore.createIndex("name", "name", { unique: false });
            userObjectStore.createIndex("email", "email", { unique: true });
        }       
    }) 
}

openDatabase()
    .then(db => {
        // db instance accessible here

    })
Enter fullscreen mode Exit fullscreen mode

Add data

Now we have db object accessible in openDatabase() promise. We can use this object to add/read/delete the data from IndexedDB.

(async function() {
    const db = await openDatabase();

    // Add
    const userReadWriteTransaction = db.transaction("users", "readwrite");
    const newObjectStore = userReadWriteTransaction.objectStore("users");

    newObjectStore.add({
        userid: "4",
        name: "John Doe",
        email: "josn@gmail.com"
    });

    userReadWriteTransaction.onsuccess = function(e) {
        console.log("Data Added");
    }

})();
Enter fullscreen mode Exit fullscreen mode

Remove data

const request = db.transaction("users", "readwrite")
    .objectStore("users")
    .delete("4");

request.onsuccess = function(event) {
    console.log("Deleted!");
};
Enter fullscreen mode Exit fullscreen mode

Read and Update Data

const readTransaction = db.transaction(["users"]);
const objectStore = transaction.objectStore("customers");
const request = objectStore.get("4");

request.onsuccess = function(event) {
    console.log("User is " + request.result.name);
    const data = event.target.result;
    data.name = "John Doe";

    const updateRequest = objectStore.put(data);
    updateRequest.onsuccess = function(event) {
        console.log("Data Updated!");
    }
};
Enter fullscreen mode Exit fullscreen mode

Example

Usecase?

  1. If you have an API that always (or most of the times) returns same values, you can call API, store the response in IndexedDB and when next time user calls the API, you can return it from IndexedDB right there and maybe later call API and store the updated value.

  2. I use IndexedDB in my application PocketBook which is a Google Keep alternative where you can store your todos, goals, etc. PocketBook uses IndexedDB by default to store notebook's information. Thus you can use pocketbook even when you are offline!


MDN Docs: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
codesandbox example: https://codesandbox.io/s/indexeddb-example-trv2f
PocketBook: https://pocketbook.cc


Thank you for reading! If you have any interesting project where you're using IndexedDB, do drop the link below!

Top comments (11)

Collapse
 
skhmt profile image
Mike ๐Ÿˆโ€โฌ›

Just note, IndexedDB is very low level and wasn't really meant to be used directly by most developers.

Most people would be better off using something like Dexie.js or LocalForage (not a typo) as a wrapper over IndexedDB.

Collapse
 
raymondcamden profile image
Raymond Camden

I disagree. While IDB is not a very easy to use API, it was absolutely meant to be used by developers. Libraries do make it simpler, but there's nothing wrong with developers using IDB w/o a library.

Collapse
 
saurabhdaware profile image
Saurabh Daware ๐ŸŒป

It totally depends on what your team prefers. I use vanilla IndexedDB in one of my application where I wrote my own wrapper around IndexedDB functions and it does work pretty well for me.

Collapse
 
raymondcamden profile image
Raymond Camden

"When you create a new database, the onupgradeneeded event will be triggered."
It will be fired when you create a new database, and when you change the version number of the database. Your example doesn't show that in the open call, but you can specify a version number and increment it to make schema changes.

Collapse
 
saurabhdaware profile image
Saurabh Daware ๐ŸŒป

Yes it does fire when version is changed, I am not really sure what is exactly the usecase of the versions in IndexedDB?

I never really had to work on the versions and I wanted to keep the article "Getting started" rather than "Know everything" so I skipped the versioning to keep it simple and so that people can compare it with other database they've worked on.

Collapse
 
raymondcamden profile image
Raymond Camden

You use versions to handle the case where you need to change the objectstore. Like maybe your business needs changed, etc. When I teach IDB, I stress that you should try your best to "get it right the first time" so you don't have to handle versioning changes.

Collapse
 
mahendrachoudhary profile image
Mahendra Choudhary

Thanks @saurabh . I think I found solution for one of my problem I always kept coming across at work . My project need to work with API which return a array of more than 4k objects, for now i am using localStorage but i think indexDB could be a better alternative .Definetly going to look it forward .

But can you give clearity on few points likes does index db support on all major browser.

Collapse
 
raymondcamden profile image
Raymond Camden

IDB would absolutely be a better use for storing a large amount of data. As you are storing an array in Local Storage, you have to serialize and deserialize the entire string in order to work with it. IDB doesn't require that.

As for support, it is VERY good: caniuse.com/#feat=indexeddb

Collapse
 
trancephorm profile image
pyc

As far as I understand, this code runs in Node.js, and every time npm start command is issued, parcel compiles it for web browser, so IndexedDB API is available (it's pure web browser Javascript code, not Node.js environment). Now what bugs me is that in files supplied at Codesandbox, I haven't found any of the code displayed here in this tutorial. Where that code should be placed? This seem to be the only way that you can compile code for browser on the fly, every time it runs?

I'm n00b, I know the question may seem stupid but please enlighten me :)

Collapse
 
mx profile image
Maxime Moreau

Hi, thank you for sharing.

IndexBD is for sure a very good thing, from my side I regret the API based on events/callbacks instead of Promises (yeah, we can use libs to do so).

Collapse
 
saurabhdaware profile image
Saurabh Daware ๐ŸŒป

Yes those callbacks makes things complicated and the libraries do work pretty well. Alternatively you can even write your own wrappers for IndexedDB functions.

Thank you for reading the article Maxime๐ŸŒป