DEV Community

Cover image for IndexedDB, your offline and serverless DB in your browser with React
Fernando González Tostado
Fernando González Tostado

Posted on


IndexedDB, your offline and serverless DB in your browser with React

Some months ago I stumbled with this inherited new app in my company.

This app had some awesome and impressive features where I learned —and struggled ☠️— a ton of features that I didn't understand very well —or haven't heard of at all!

One of these cool features was using an IndexedDB for storage.

Honestly it took some time to understand where the hell the data was being stored. I thought that it was fetched everytime or cached with some sort of server magic that was beyond my reach. However, after diving deeper in the code I discovered 💡 that it was using an indexedDB to store the data in the browser, therefore saving a lot of unnecessary requests.

So what's an Indexed DB?

"IndexedDB is a low-level API for client-side storage of significant amounts of structured data, including files/blobs."

Key Concepts

  • Asynchronous, therefore it won't block your main thread operations ⚡.
  • A noSQL database, which makes it very flexible —and dangerous ☢️.
  • Lets you access data offline.
  • Can store a large amount of data (more than other web storage like localStorage or sessionStorage).

Okay, so once I had a better idea of what an IndexedDB was, I wanted to practice a bit with a simple implementation instead of having to painfully try it with the super complex codebase of my inherited real life application.

Implementation in a React App

This is a low-level API, therefore its implementation is kind of weird, but don't be scared, after some practice everything falls into its place. While there are some third app libraries to handle it —like Dexie—I wanted to try the raw API.

All the methods used here will return a Promise, this way we'll be able to get a response in our component of what's happening in the DB. If you wish, you don't have to wait for a promise to be resolved, it's fine and actually how you'll see the implementation in most pages out there. I like the promise approach for better understanding what's going on 👀 but this approach of course will block the main thread until the promise is resolved.

Initializing the DB

First step, declare the db and open (or init) it in the user browser

This initDB method will basically open the connection myDB —we can have several DB's and this is the tag that identifies them— and then we'll attach two listeners to request.

The onupgradeneeded listener will only be fired when a) we create a new DB b) we update the version of the new connection with for example indexedDB.opn('myDB', version + 1).

The onsuccess listener will be fired if nothing went wrong. Here's where we will usually resolve our promise.
The onerror is self explanatory. If our methods are correct we'll rarely hear from this listener. However, while building our app it will be very useful.

Now, our component will show this super simple UI

first ui before init

If we call the handleInitDB the DB will be created and we'll be able to see it in our dev tools/application/IndexedDB tab:

dev tools - indexed db table

Hooray! We have the DB up and running. The DB will persist even if the user refreshes or loses her connection 🔥.

Init ready

Btw, the styling is up to you 😂.

Adding data

For the moment we have an empty DB. We are now going to add data to it.

What's new here?

No onupgradeneeded anymore. We don't make changes to the store version, therefore this listener is not required.

However, the onsuccess listener changes. This time we'll write on the db using the transaction method, which will lead us to be able to make use of the add method, where we are finally passing our user data.

Here Typescript helps us to avoid mistakes when passing our data and keeping our DB integrity intact by accepting a <T> generic type, which for this case will be the User interface.

Finally, in our onerror I didn't want to lose much time for this, it will resolve a string, but it could be an Exception or something similar.

In our component we'll add this simple form to add users.

add user form

If we add the user and go to our application (don't forget to refresh the database for the stale data) we'll see our latest input available in our IDB.

IDB with data

Retrieving data

Ok, we are almost there.

Now we'll get the store data so we can display it. Let's declare a method to get all the data from our chosen store.

And in our jsx component

And et voilà! We see our data in the table and this data will persist in the browser 👏.

users table

Finally, to make this more dynamic we can make any of theses processes without all these buttons since we receive promises:

Deleting rows

Finally, we'll understand how to delete data from the DB. This is quite easy, however, we'll have to have in mind which is our Key Path —a.k.a. Unique identifier or Primary Key— for the entries of the selected store. In this case we declared that the id will be that identifier

// db.ts/initDb
db.createObjectStore(Stores.Users, { keyPath: 'id' });
Enter fullscreen mode Exit fullscreen mode

You can also verify it directly by inspecting your store in the dev tools

identify your key path

For more advanced keys you can make use of other Key Paths, however we won't cover those cases here, so using the id as KP is the expected pick.

The method to delete the selected row will accept a storeName ('users' in this case), and the id as parameters.

Promises should always be handled with a try/catch block.

And in the component a handler to remove users, and of course a button to pick the desired element in the table

And the element should be gone after clicking the Delete button 🪄.


To be honest, I think that this API is weird and it feels a lack of implementation examples (for React at least) and documentation —besides a few great ones of course— and it should IMO be used only in a few specific cases. It's always nice to have options, and if it's your case, go ahead, I've witnessed a real production use case in an app that has thousands of users, and I must confess that the IDB has been the feature that has brought me the least trouble. This tool has been out there for several years already, so it's stable enough to be safely used.

If you want to check the full code, here's the repo.



Photo from Catgirlmutant on Unsplash


An interesting approach with promise chaining

Top comments (0)

Timeless DEV post...

Git Concepts I Wish I Knew Years Ago

The most used technology by developers is not Javascript.

It's not Python or HTML.

It hardly even gets mentioned in interviews or listed as a pre-requisite for jobs.

I'm talking about Git and version control of course.

One does not simply learn git