« All posts

03.16.2022 - JavaScript / Moving from LocalStorage to IndexedDB

LocalStorage is a popular solution to persists data (locally) across sessions for web applications, but there are some limitations, for example:

  • It is limited to 10MB on most browsers. On Safari, it’s 5MB
  • It can only store DOMString data (for both key and values). If you have a complex object, you have to encode it, for example, using JSON.stringify.
  • It’s blocking, while the data is writing or reading, your code blocks
  • It can’t be used in web workers, so you cannot move the intensive data handling task off the main thread.

On the other hand, IndexedDB free you up from some of the LocalStorage’s limitations:

  • You can use up to 50% of the free disk space (on Firefox), or 80% on Chrome. The limit is 1GB on Safari, but there will be 200MB increments every time you hit the limit.
  • It can store any object, File, or ArrayBuffer. So, no more JSON.stringify.
  • Writing and reading are asynchronously (but IndexedDB does not provide any Promise interface, more on this later)
  • It can be used in web workers!

The downside is the API of IndexedDB is cumbersome, while it’s not providing any Promise interface, you have to create a handler to handle every event for every operation, for example, to open a DB:

let db;
const  request = indexedDB.open("TestDatabase");
request.onerror = event => {
request.onsuccess = event => {
    db = event.target.result;
request.onupgradeneeded = event => {
    db = event.target.result;
    // object store a.k.a tables, can only be created
    // inside onUpgradeNeeded
    var objectStore = db.createObjectStore("table-name");

The same with reading or writing data:

const request = db
request.onsuccess = event => {
    // request.result

For a more detailed introduction on how to use the IndexedDB APIs, check this document https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB

Also, on Safari, there is a 7 days limit to store any data, regardless of LocalStorage or IndexedDB.

It’s also important to note that all client-side storage solutions should only be used to store non-sensitive data. Any script running on the page can access them.

To make things easier when using IndexedDB, there are a lot of libraries that wrap the IndexedDB API and provide the Promise-based interface, like IDB or localForage.

To replace localStorage in your project with IndexedDB, you can mimic the API of localStorage, like setItem, getItem, and wrap it with IDB, or use localForage directly.

Below is the localStorage-like interface implemented using IDB:

import { openDB } from 'idb';
const DB_NAME = "localStorage-DB";
const TABLE_NAME = "keyValuesStore";
const DB = async () => await openDB(DB_NAME, 1, {
    upgrade(db) {
export const Storage = {
    get: async (key) =>
        (await DB()).get(TABLE_NAME, key),
    set: async (key, value) =>
        (await DB()).put(TABLE_NAME, value, key),
    del: async (key) =>
        (await DB()).delete(TABLE_NAME, key),
    clear: async (key) =>
        (await DB()).clear(TABLE_NAME),
    keys: async () =>
        (await DB()).getAllKeys(TABLE_NAME)

Then you can go and replace your localStorage usage with the new Storage:

- localStorage.setItem('key-001', JSON.stringify(value));
+ Storage.set('key-001', value);
- const obj = JSON.parse(localStorage.getItem('key-001') ?? '{}');
+ const obj = Storage.get('key-001');

You can check the following documents for more detailed information on IndexedDB and other storage solutions for web apps:

Read more