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

saurabhdaware

Saurabh Daware 🌻

Posted on February 12, 2020

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!

💖 💪 🙅 🚩
saurabhdaware
Saurabh Daware 🌻

Posted on February 12, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related