Beginner's Guide to IndexedDB: Illustrated with Easy-to-Follow Block Diagrams

Beginner's Guide to IndexedDB: Illustrated with Easy-to-Follow Block Diagrams

ยท

6 min read

What is IndexedDB?

In simple terms, IndexedDB is a client-side storage technique like LocalStorage and SessionStorage. But what sets it apart and makes it far more powerful than the other alternatives is it's:

  • Ability to manage large amounts of structured data including files/blobs or other binary types

  • Ability to create indexes for efficient querying of data (and hence the name "Indexed" DB)

  • Support for transactions, ensuring data integrity and consistency, especially in concurrent operations.

Building blocks of an IndexedDB

Block diagram showing the building blocks of IndexedDB

  • Database: Represents the entire IndexedDB database. It contains one or more object stores.

  • Object Store: Similar to tables/collections in traditional databases. It holds records of structured data.

  • Indexes: Optional structures within object stores for efficient querying of data records.

    Eg;

    Say we have an object store for storing user details {name, city, age}. If we want to retrieve the list of users by city, we can create an index for the city property. This index allows us to fetch records efficiently without searching through the entire dataset.

    Index example

  • Keys: Unique identifiers for each record in an object store. Similar to the primary key in RDBMS.

  • Data: The actual structured data stored within the object store.

IndexedDB Operations

The operations performed on an IndexedDB can be divided into two phases

  1. Creation/Initialisation Phase

  2. Updation/Querying Phase

  1. Creation/Initialisation Phase

In this phase, we will be creating the database, followed by the ObjectStore which would eventually hold our data records.

Block diagram for indexedDB creation phase

We start by opening a connection to the database, if the database doesn't exist it will be created.

const openRequest = indexedDB.open(<db-name>, <db-version>)

It returns an IDBOpenDBRequest object which has handlers that can be used to handle different connection scenarios like:

  • onerror: Connection could not be established.

  • onblocked: Connection was blocked due to a conflicting connection to the same database, which typically occurs when the same app is open in multiple tabs.

  • onsuccess: Connection was established successfully and the database can be accessed at openRequest.result

  • onupgradeneeded: Connection was established, but the database doesn't exist, so it needs to be initialized or is outdated and requires syncing with the latest version.

    Most importantly this handler is where we create new object stores and indexes for object stores.

Upgrade needed block diagram

  1. Updation/Querying Phase

Now that we have our database with an object store ready, let's start performing some basic CRUD operations.

IndexedDB allows us to create transactions to carry out operations on the Object Store data.

What is a transaction?

A transaction can be considered a wrapper around a group of operations to be executed on the database. It guarantees data integrity by ensuring that either all operations within a transaction succeed or none of them do.

Say, we have an e-commerce site, whenever a user adds an item to the cart we also need to update the item quantity, if either of the operations fails it should roll back to the initial state.

Let's explore the sequence of steps required to perform an operation or a set of operations on an Object Store:

Transaction steps

For a better understanding, we will try to implement the e-commerce example we discussed above

  • We begin by creating a transaction. We must specify the Object Stores that the transaction will interact with. If it requires write access, the mode should be set to readwrite. This ensures the stores remain locked for other transactions until the current transaction is finished.

      const txn = db.transaction(["cart","items"], "readwrite")
    
  • We obtain the object stores on which we will be performing operations.

      const cartStore = txn.objectStore("cart"); 
      const itemsStore = txn.objectStore("items");
    
  • Finally, it's time to execute the set of operations as follows:

    • Get the item from itemsStore using the get method

    • If the item is available, reduce the quantity and update itemsStore using the put method

    • Add new cart item to cartStore using the add method

    const itemId = 123;
    const getItemRequest = itemStore.get(itemId); // 1st operation

    getItemRequest.onsuccess = function(event) {
            const item = event.target.result;

            if (item.quantity >= 0) {
                // Item available, reduce the quantity
                item.quantity -= quantity;
                // Update item inventory (2nd operation)
                const updateItemRequest = itemsStore.put(item); 

                updateItemRequest.onsuccess = function(event) {
                    // Create new cart item
                    let newCartItem = {
                        itemId: productId,
                        status: "Pending"
                    };

                    // Add to cart object store (3rd operation)
                    let addToCartRequest = cartStore.add(newCartItem);

                    addToCartRequest.onsuccess = function(event) {
                        console.log("Added to cart successfully.");
                    };

                    addToCartRequest.onerror = function(event) {
                        console.error("Error adding to cart:", event.target.error);
                    };
                };

                updateItemRequest.onerror = function(event) {
                    console.error("Error updating item inventory:", event.target.error);
                };
            } else {
                console.error("Item out of stock.");
            }
        };

    getItemRequest.onerror = function(event) {
            console.error("Error retrieving item information:", event.target.error);
        };

In the above example, we observed several methods like get, put, and add. Now, let's delve into the various methods available depending on the type of operation we intend to execute on an Object Store.

Adding/Updating

Deleting

Querying

If we want to query on top of an existing index we can open an index and execute all the existing methods like get, getAll, getKey and getAllKeys on top of it.

// querying on an index
const cityIndex = userStore.index("city");
const getRequest = cityIndex.getAll("Bangalore");
getRequest.onsuccess = function() {
  if(getRequest.result) {
    console.log("Users from Bangalore", getRequest.result);  
  }
};

Cursors

IndexedDB also provides another tool called cursor which is particularly helpful for querying large datasets efficiently. It allows one to traverse through records one by one without loading the entire dataset into memory, which can be beneficial for handling large amounts of data.

To open a cursor we need to use the openCursor() method on a store or index and then iterate over the result using advance or continue methods.

const request = store.openCursor();

request.onsuccess = function() {
  const cursor = request.result;
  while (cursor) {
    console.log(cursor.value);
    cursor.continue();
  } 
};

We will delve deeper into cursors in an upcoming article if needed, please let me know in the comments.

Conclusion

Glad that you've made it this far, kudos to your patience! ๐Ÿ‘ I hope I've been able to provide some value.

So to sum it up, we saw what IndexedDB is and what sets it apart from other client-side storage techniques, its building blocks like transactions, object stores, and indexes, and phases involved in creating and updating/querying the database and different methods available to perform CRUD operations on the database.

Lastly, we also saw a brief about cursors and how it is beneficial in handling large datasets.

Ending note: I highly recommend going through the references mentioned below to solidify your understanding and to get an in-depth knowledge about IndexedDB.

References

ย