Replacing LocalStorage with IndexedDB

Keagan Chisnall
5 min readOct 4, 2018

--

Yesterday I was building a prototype in Javascript and I needed an extremely simple and local database to hold some data between sessions. The go-to choice was LocalStorage because it’s so unbelievably easy to use; BUT, objects have to be serialized 😒, and classes are have other overheads which are annoying 😩. So what about IndexedDB…

Well, I’m new IndexedDB. Really new. So new that I constantly type IndexDB which seems like a better name. My first impressions weren’t great. It felt really clunky and some of the ways to do simple things seemed really convoluted. I’m sure there’s a reason; but either way, it is local and can store objects 👍 (not classes though 😢).

So I made a helper so that it became effectively LocalStorage that could handle objects and I thought I’d share it here. For those who read code more easily than text, jump to the JS code here in the SimpleIDB object (the Vue code below it is just for the UI — duh).

Understanding IndexedDB

OK, now I’m sure friendly citizens of the internet will provide you with corrections in the comments, but here’s what I learned after a couple hours of reading:

  • “Opening” a database doesn’t return a reference to the requested database — it actually returns a response request that you can assign functions to.
  • The types functions you can assign to include things like onsuccess(), onerror(), etc. but if you’re creating or restructuring a database you will need to use onupgradeneeded()
  • What this onupgradeneeded() function also does is increases the version number of the database every time it’s run, which may be useful for production (because you can detect and update out-of-date structures), but it also means you have to count and keep track of structural changes— which is the exactly type of overhead I didn’t need, so I only wanted to run this once (and stay with version 1).
  • Within a database, there are ‘stores’ (like tables for SQL), which must be created inside this onupgradeneeded() function. These stores are where your data actually lives and what you work with.
  • BUT! You (again) don’t just get a reference to manipulate. You need to get a “transaction” request from the database (and tell it what store you want readonly or readwrite privileges for), and then use that transaction to get the store object, which you can then send a request to get another response request to assign functions to (like with the database)… Simple right?? 😒

Anyway, there are plenty more features and functions (with more steps), but for my little LocalStorage replacement, that’s all you need.

The Plan

Generally you only use one database per site, and in my particular case, I only wanted one store which I could add simple keys and values to. This meant I could name and refer to my database as ‘myDatabase’ and store as ‘myStore’. I did originally define these as variables but assigned functions couldn’t access the helper’s ‘this’ context meaning more hacking… so I just hardcoded them.

I also wanted 4 basic functions: initialize(), get(), set(), and remove(); and I wanted them to return promises… because async/await capability rocks.

So we start with:

SimpleIDB = {
initialize () {
return new Promise((resolve, reject) => {})
},

get (key) {
return new Promise((resolve, reject) => {})
},

set (key, value) {
return new Promise((resolve, reject) => {})
},
remove (key) {
return new Promise((resolve, reject) => {})
}
}

The Code

For the initialize() function, we start with “opening” the ‘myDatabase’ database (it creates it if it doesn’t exist), and then assign functions to onupgradeneeded() (because we want to add a store) and onerror() (because we’re not animals). The ‘result’ property of this request is actually the database reference, and that’s what we use to get the createObjectStore() function to add a store. There’s also an error property you can reference:

initialize() {
return new Promise((resolve, reject) => {
let request = indexedDB.open('myDatabase')
request.onupgradeneeded = function() {
request.result.createObjectStore('myStore')
resolve()
}
request.onerror = function() {
reject(request.error)
}
})
},

OK, now get(), set(), and remove() all follow the same code and only differ on whether the transaction is readonly/readwrite, and which store function is called. We’ll look at get(). First we need to open the database, and this time we’ll use onsuccess(). With onsuccess() we can then use the result property (aka database reference) to generate a transaction for the store we want (i.e. ‘myStore’) and as we’re not writing anything, ‘readonly’ access is enough. Then we can use that transaction to get a store reference and use the store’s get() function to make another request which we can attach functions to. For set we use the store’s put() function, and for remove we use the store’s delete() function.

get(key) {
return new Promise((resolve, reject) => {
let oRequest = indexedDB.open('myDatabase')
oRequest.onsuccess = function() {
let db = oRequest.result
let tx = db.transaction('myStore', 'readonly')
let st = tx.objectStore('myStore')
let gRequest = st.get(key)
gRequest.onsuccess = function() {
resolve(gRequest.result)
}
gRequest.onerror = function() {
reject(gRequest.error)
}
}
oRequest.onerror = function() {
reject(request.error)
}
})
},

That’s it. At least for my little helper. There are in fact lots of other features and options like indexes, auto-increments, overwrite rules, etc. So to get a deeper understanding of IndexedDB I actually found the examples on the painfully boring manual to be most helpful/consistent. All the other tutorials I read used different methods to each other, deepening my belief IndexedDB is not the hero it could have been… Anyway, at least I can now store objects locally 😎

I made an example app here with four notes:

  1. The example uses Vue for the UI
  2. I inserted a delete database function in initialize() to always start fresh
  3. While you have to add JSON in the textbox, the code converts it to an object before it adds it (so it does actually store objects)
  4. You need to use your browser to view your IndexedDB database (a database viewer is a-whole-nother project)

Any questions/corrections, please comment.

--

--

Keagan Chisnall
Keagan Chisnall

Written by Keagan Chisnall

I have a background in process engineering, management, and consulting; but my passion is making systems and processes more efficient. Visit me: chisnall.io

Responses (2)