A Beginner’s Guide to IndexedDB
As a developer, you will have to interact with data one way or the other, since we all know how valuable it is. You might have worked with databases on the server side, such as MySQL or MongoDB, or even local storage on the client side. These are all fine, and they come with their perks and problems, but what if there was something with a bit of backend functionality that you could implement on the browser? Now, that’s where IndexedDB comes in.
In this article, we will thoroughly explore the perks and the ins and outs of this awesome but surprisingly less talked about technology. Dive right in.
Overview of how storage works on the client side of the web
Client-side storage and caching can significantly improve user experience, reliability, and website performance. Storage systems like the Caching storage API
allow us(developers) to store our static resources like HTML, Javascript, and CSS, ensuring they are always instantly available to the user.
Meanwhile, IndexedDB is used for storing other data like user documents, settings, and more. IndexedDB and Caching Storage API
are reliable systems for storing data on the front end because they are asynchronous and supported by every modern browser.
Although several storage mechanisms exist on the browser, these other mechanisms come at a cost as most have limited use, are synchronous(i.e., they can block the main thread), and can cause significant performance issues.
Unless your website deals with large amounts of data(like videos, pictures, documents, and gaming assets from tons of users), modern browsers with storage mechanisms like IndexedDB can handle a fair amount of data on the client side. These front-end storage mechanisms have different client-side storage capacities based on browsers.
Firefox allows up to 2 GB of front-end storage from a single website, Safari 1 GB, and Chrome 80% of a particular hard disk. So if you had a hard disk of 100GB, the total amount of data that could be stored on the front for Chrome would be 80GB, whereas a single website can use 60 GB of that storage capacity.
Sometimes an app could store data over the quota for itself, leading to a “quota exceeded error” from asynchronous front-end storages like IndexedDB and caching storage API
. However, this should also be a topic for later.
Client-side data can be cleared by the user choosing to delete site data or is deleted from the user’s least used websites automatically when disk space is low. If the website cannot sync data with the server due to too much data stored on the front end when disc space is low, the website will not function properly, leading to a poor user experience.
Description of IndexedDB
Now let’s get indexed(Dad joke, lol). This section of the article will look at IndexedDB at a higher level. It’s a powerful technology, and understanding how it fits in can help you during system design decisions. (Drum roll…).
IndexedDB is a low-level API for client-side storage; you might know popular ones like local storage and cookies, so IndexedDB is simply another way to implement storage(maybe more?). Yes, you guessed right! IndexedDB is more.
Why? because it is an actual database that works in the browser. IndexedDB functions like a non-relational database with a key-value pair (Think MongoDB). The main difference from other databases is that we don’t have direct access to it since it functions as part of the browser.
IndexedDB is not a relational database as it doesn’t use SQL, but IndexedDB is a database that provides an object store. IndexedDB lets you create a database, with each database having multiple object stores. Although this object store assigns a unique key to each object, you can also define indices for each object store.
Implementation of IndexedDB
Let’s work through some examples.
Visualizing Code Flow
To be able to access IndexedDB in your browser, follow these steps:
-
Open up your Chrome devtools by using Option + ⌘ + J (on macOS),or Shift + CTRL + J (on Windows/Linux).
-
Go to the application tab.
- Click on the section, IndexedDB.
- Inside it, there will be stores or collections(MongoDB jargon)
- Inside the collection of stores, you have the document. A document is simply a Javascript object.
We would be using the IDB-KEYVAl
library by Jake Archibald. This allows IndexedDB to be used like MongoDB, where we have key+value pairs.
This library is different from local storage because:
- IndexedDB does not need to use
JSON.parse
orJSON.stringify
methods; and - IndexedDB can store binary data(BLOBs)
Key things to note about IndexedDB key-Val
library:
- It does not allow for creating multiple stores in a database. So it is one store per database.
- This library does not have cursors or transactions.
So, straight to business…
Getting Started
Import the methods we would need for this tutorial
import {
get,
set,
update,
del,
values,
keys,
clear,
entries,
createStore,
} from "https://cdn.jsdelivr.net/npm/idb-keyval@6/+esm";
// default Database name in this library and example is "keyval-store"
// default Store name is keyval
// all the methods in this library resolve to promises.
Create Store Database
Please note that if you create a new database, you have to specify it as a third; otherwise, the object would be saved as to the default database, keyval-store
.
To create and use a new database:
let rooms = createStore("showroomDatabase", "showroomStore");
let branchInfo = {
name: "Yenagoa",
manager: "King",
};
set("toyotaRoom", branchInfo, rooms)
.then(() => {
console.log("saved toyota");
})
.catch(console.warn);
- The create store method takes two arguments, one is the name of the database, and the second is the store’s name.
- If you are using any other method, you need to do is specify a third argument and call the name of the new database you are using.
Set Method
With this method, you can populate the database and store with objects
let toyotaBrand = {
id: "001",
name: "Toyota",
factory: "Japan",
};
let ferrariBrand = {
id: "002",
name: "Ferrari",
factory: "Italy",
};
let jeepBrand = {
id: "003",
name: "Jeep",
factory: "USA",
};
function setObject() {
set("toyota", toyotaBrand)
.then(() => {
console.log("saved toyota");
})
.catch(console.warn);
set("ferrari", ferrariBrand)
.then(() => {
console.log("saved ferrari");
})
.catch(console.warn);
set("jeep", jeepBrand)
.then(() => {
console.log("saved jeep");
})
.catch(console.warn);
}
setObject();
- To store an object, we use the
set
method after creating the objects. (N/B: This object would be stored in the default library since we did not specify a new database and store). - First, we define a function to use the
.set()
method. - When we call the
set
method, it takes in two parameters. The first parameter is the key, and the second is the object’s value you want to store. - Since this method resolves to a promise, we can chain
.then()
and.catch()
methods on it. If the promise is resolved, then tell us it’s saved, or else do something like warn us. - To store an additional object, simply create an object and use the
.set()
again. It will store the object in the default database.
NOTE:.set()
will overwrite old values if it is called with the same key.
Get Method
To retrieve an object within a store, you must call get
and the name of the key you want to retrieve.
function getObject() {
get("toyota")
.then((object) => {
console.log(object);
})
.catch(console.warn);
}
getObject();
- The first
.get()
method calls the key of the first car,firstCar
. - The
.then()
method accesses the object value of the first car and grants you access to play with it. - If you try running the code, you observe that we can access all the object’s properties.
.catch()
catches errors(pun intended).
Update Method
If you want to change an object and its information, we can use the .update()
method provided by the key-val library.
function updateObject() {
update("toyota", (val) => {
return (val.name = { ...val, name: "hyundai" });
}).then(() => {
console.log("successfully updated");
});
}
updateObject();
The update method takes two arguments; the first is the key of the object you are trying to update, and the second is a function that allows us to tap into the value of the key(in this case, an object) and change what we want to.
Note: You must return the change, or it won’t work.
Getting All Objects
You can choose to get all keys of the objects in a store with:
function listKeys() {
keys().then((keys) => console.log(keys));
}
listKeys();
Or you can list out all the values of the keys with:
function listValues() {
values().then((values) => console.log(values));
}
listValues();
To get all objects ever stored in the database, you can use the entry method, which lists items from the store by index.
function listObjects() {
entries().then((entries) => console.log(entries));
}
listObjects();
Del Method
The Del method is used for deleting objects from the store. All you have to do is call the del
method and enter the key of the object you want to delete.
function deleteObject() {
del("toyota").then(console.log("successfully deleted"));
}
deleteObject();
Clear Method
The .clear()
method is used to clear the database.
function clearDatabase() {
clear().then(console.log("Database is successfully cleared"));
}
clearDatabase();
Session Replay for Developers
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.
IndexedDB vs. LocalStorage
IndexedDB has a significant edge over local storage because:
- IndexedDB can store almost any value, whereas local storage can only store strings.
- IndexedDB supports Web workers, and as you have correctly guessed local storage doesn’t allow us to use web workers.
- Local storage is synchronous and can cause UI problems such as thread blocking, but IndexedDB is asynchronous.
- IndexedDB can store around 2 GB of front-end data, however, local storage offers only a paltry capacity of 2MB -10 MB.
- Both have full browser support, so you’ll get to make your choice.
Use Cases
In real time, you could implement IndexedDB in the following scenarios
- You want the edits made by the user on the website to be stored in the browser without the website having to sync back with the server every time an edit is made(Think Google Docs).
- You need a more robust and good user-experience enhancing system to cache website assets such as fonts and images.
Limitations:
For every technology, there’s a trade-off, and the limitations of IndexedDB in the real world are
- To be aware that even though IndexedDB reduces the occurrence of UI blockage and interference.
- It can also cause UI blocking. This happens because IndexedDB works by making a copy of the website’s front-end data, and the larger the data it copies, the higher the probability of UI blockage.
- You can play around with UI blocking by not trying to store the whole website’s state at once but breaking down what needs to be stored and using an array buffer to represent the data stored in binary format.
Benefits
When making system design decisions, these benefits might influence your choices:
- IndexedDB can store significant amounts of data.
- It is also very fast because it uses indexes to search for data.
- IndexedDB also works well with Offline Web apps since its queries will still work even though the web app is not connected(Think to-do lists and notepads)
- IndexedDB syncs efficiently with web workers. i.e., Web workers allow already loaded web content(Javascript) to run and sync with the servers without affecting scripts interfacing with the user.
- And the cream of the crop has an asynchronous API, which means it can perform multiple operations at once.
Conclusion
IndexedDB is a fantastic tool for client-side storage. In this article, we have looked at what IndexedDB is, how IndexedDB is different from local storage, a glimpse into how the browser stores client data, how to implement IndexedDB, and the benefits and real-life use cases of IndexedDB.
If you care for any advice before I hand over this ring of power is that IndexedDB is a very complex tool that should be considered when working with complex object storage. But if you need to work with simple value pairs, then synchronous storage mechanisms like local storage will suffice.