IndexedDB made easy like localStorage
I’m nearly done rewriting my browser extension, A Fine Start, moving away from Vue 2 and using vanilla JavaScript. I’m calling this the final rewrite, as I’m trying to make it as easy for me to maintain going forward as possible, using as few dependencies as I can get away with.
A Fine Start is a new tab page replacement. It shows columns of text links on your new tab page, which you can organize into groups and sort how you see fit. I’ve been using it (and previously, its ancestor) every day for almost a decade now. I love it.
In the current release of my extension, those columns of links are stored as JSON in localStorage. Kind of. If you are using the web hosted page, that’s true. If you are using the browser extension, then it’s using browser.storage.local
, which acts like local storage for all intents and purposes but is the special browser extension kind. I’m not 100% sure what all the differences are, but best I can tell, extension storage is less likely to be purged by the browser and operations for interacting with storage are asynchronous.
But localStorage has some disadvantages that become annoying the more you work with it. For me, the biggest one is that you can only save strings. If you have a big nested object (like columns of text links that are sorted into groups) you first need to JSON.stringify
that sucker before you can save it.
Another downside is that you only get about 5MB of storage space theoretically. That’s a pretty good bit, so maybe you won’t run into that. I don’t think I’ve run into that either. But it’s something to be aware of depending on what kind of app you’re making.
Because my app has a web version plus extension versions, it needed to work with the various APIs for storage depending on whether it’s running in the context of a webpage or a browser extension. I wanted to use Just One Thing no matter the context.
So I turned my attention to IndexedDB, the fancypants if somewhat stuffy database system that’s built into the web browser. The nice thing about this is that it can store much more data than localStorage. And most importantly,[1] you can save data other than strings. If you have an object, you can just save that object directly in the database—no stringification needed.
But using IndexedDB is pretty annoying and verbose. Especially since I’m not really using any of its features—I’m still just saving key value pairs for the most part. Luckily, a library called localforage grabbed my attention. It lets you use easy old localStorage methods like getItem
and setItem
but saves data in IndexedDB. I integrated it deeply into my app before running into an issue that I learned was not going to be fixed because the project is purportedly to be archived soon.
Well, dangit.
That’s fine, I thought to myself. I didn’t want to bring in a bunch of extra bloat anyway. I will just Do It Myself™. So I started writing a class and before I knew it I was putting the finishing touches on a full blown npm JavaScript package.
Introducing DataStore
I decided I wanted the benefits of IndexedDB, even though I’m not saving and querying complex data. Specifically DataStore offers these benefits over localStorage:
- More storage space
- Save structured data without
JSON.stringify
- Asynchronous operations
- Multiple stores
It’s easy like localStorage, though it does have this one a bit of extra setup:
1const store = new DataStore();
Once you do that, you can perform all of the CRUD operations you are used to.
1async myFunction() { 2 const store = new DataStore(); 3 4 await store.setItem('email', 'blake@example.com'); 5 6 const email = await store.getItem('email'); 7 console.log(email); // blake@example.com 8 9 await store.removeItem('email');10}
Okay, that’s the easy mode. Behind the scenes, DataStore is creating a default database with a default data store and saving everything to that store. But if you have more advanced needs, you may want to specify a database name and one or multiple stores in which to put data.
In that case, you can set up a database first before using it.
1DataStore.setupDb({2 name: 'My Database',3 storesToCreate: ['posts', 'comments'];4});
Now you can use those stores to save different groups of data.
1async myFunction() { 2 const postStore = new DataStore('My Database', 'posts'); 3 4 // we can save structured data directly! 5 await postStore.setItem(3, { 6 title: 'My third post', 7 body: 'In west Philadelphia born and raised...' 8 }); 9 10 const commentStore = new DataStore('My Database', 'comments');11 12 await commentStore.setItem(3, [13 'FIRST',14 'Remember the playground?',15 'YES, where we spent most of our days'16 ]);17}
Okay if you were actually saving blog posts and comments in browser storage, you would probably want to use IndexedDB directly so you could write queries and what not. But just as an example, this is how you can easily save structured data.
It’s fast, convenient, and flexible storage.
How do I get it?
I published DataStore as an npm package, so if that’s your kind of thing, you can:
1npm i @blakewatson/datastore
But if you are like me and tired of npm and build steps, you can download the ESM or global version from the CDN or GitHub release.
I haven’t used this heavily yet, so it’s extremely possible that there are bugs. Please open an issue if you run into anything.
Check out the readme on GitHub for more details installing and using DataStore.
Most importantly, for me. I imagine if you had a bunch of data you wanted to query, than actual database features would be among the most important. But remember, all I’m trying to do here is replace localStorage. ↩︎