IndexedDB tutorial: store data in the browser with JavaScript

IndexedDB tutorial: store data in the browser with JavaScript

IndexedDB is one of the storage solutions browsers have picked up over the years.

It is a key/value database (a noSQL database) generally seen as the definitive answer for storing data in the browser.

The API is asynchronous, which means your code will not block the rest of your program while it runs, a real win for the user experience. It can store an unlimited amount of data, although past a certain threshold the user is asked to allow the site to store more.

Other options exist: cookies and Web Storage (localStorage and sessionStorage). Local and session storage have the drawback of being limited to a small size, browsers offering between 2MB and 10MB per site.

While you can technically create several databases per site, you normally create a single one, and inside that database you can create several object stores.

A database is private to a domain, so no other site can reach another IndexedDB database.

Each object store usually holds a set of things, which can be:

  • objects
  • arrays
  • strings
  • numbers
  • dates

For example, you might have one store holding posts and another holding comments.

A store holds a number of objects, each with a unique key, which is how an object is identified.

You change those objects through transactions, adding, updating, deleting and iterating over the items they contain.

Since Promises arrived in ES6, and APIs moved over to them, the IndexedDB API feels a little old school.

There is nothing wrong with that, but in all the examples below I will use Jake Archibald’s IndexedDB Promised library, a thin layer over the IndexedDB API that makes it easier to work with.

That library is also the one used in every IndexedDB example on the Google Developers site


Creating an IndexedDB database

The simplest way is to use unpkg, adding it to the head of the page:

javascript
<script type="module">
import { openDB, deleteDB } from 'https://unpkg.com/idb?module'
</script>

Before using the IndexedDB API, always check that the browser supports it. Even though support is widespread, you never know which browser someone is running:

javascript
(async () => {
  'use strict'

if (!('indexedDB' in window)) {
    console.warn('IndexedDB not supported')
    return
  }
})()

How to create an IndexedDB database

Use openDB():

javascript
(async () => {
  //...

const dbName="mydbname"
  const storeName="store1"
  const version = 1
  const db = await openDB(dbName, version, {
    upgrade(db, oldVersion, newVersion, transaction) {
      const store = db.createObjectStore(storeName)
    }
  })
})()

The first 2 parameters are the database name and the version. The third one, which is optional, is an object holding a function that is only called when the version number is higher than the version of the database currently installed. Inside that function you can upgrade the structure of the database (stores and indexes).

Adding data to a store

Adding data while creating the store, initialising the store

To add data you use the object store’s put method, but the store has to be initialised first with db.createObjectStore() when we create it.

With put, the value comes first and the key second. That is because if you set a keyPath when creating the object store, you do not have to pass the key name on every put() call, you can simply write the value.

This fills store0 as soon as we have created it:

javascript
(async () => {
  //...
  const dbName="mydbname"
  const storeName="store0"
  const version = 1

const db = await openDB(dbName, version,{
    upgrade(db, oldVersion, newVersion, transaction) {
      const store = db.createObjectStore(storeName)
      store.put('Hello world!', 'Hello')
    }
  })
})()

Adding data once the store already exists, using transactions

To add items later on, you need to create a read/write transaction, which keeps the database consistent (if one operation fails, every operation in the transaction is rolled back and the state goes back to where it started).

To do that, use a reference to the dbPromise object we got back from openDB, and run:

javascript
(async () => {
  //...
  const dbName="mydbname"
  const storeName="store0"
  const version = 1

const db = await openDB(/* ... */)

const tx = db.transaction(storeName, 'readwrite')
  const store = await tx.objectStore(storeName)

const val="hey!"
  const key = 'Hello again'
  const value = await store.put(val, key)
  await tx.done
})()

Getting data out of a store

Getting one item from an object store: get()

javascript
const key = 'Hello again'
const item = await db.transaction(storeName).objectStore(storeName).get(key)

Getting every item from an object store: getAll()

Getting every stored key

javascript
const items = await db.transaction(storeName).objectStore(storeName).getAllKeys()

Getting every stored value

javascript
const items = await db.transaction(storeName).objectStore(storeName).getAll()

Deleting data from IndexedDB

Deleting the database, an object store and data

Deleting an IndexedDB database entirely

javascript
const dbName="mydbname"
await deleteDB(dbName)

Deleting data inside an object store

We use a transaction:

javascript
(async () => {
  //...

const dbName="mydbname"
  const storeName="store0"
  const version = 1

const db = await openDB(dbName, version, {
    upgrade(db, oldVersion, newVersion, transaction) {
      const store = db.createObjectStore(storeName)
    }
  })

const tx = await db.transaction(storeName, 'readwrite')
  const store = await tx.objectStore(storeName)

const key = 'Hello again'
  await store.delete(key)
  await tx.done
})()

Migrating from a previous version of a database

The third parameter of openDB() (optional) is an object that can hold an upgrade function, called only when the version number is higher than the version of the database currently installed. Inside that function you can update the structure of the database (stores and indexes):

javascript
const name="mydbname"
const version = 1
openDB(name, version, {
  upgrade(db, oldVersion, newVersion, transaction) {
    console.log(oldVersion)
  }
})

Inside that callback you can check which version the user is upgrading from and act accordingly.

You can migrate from a previous database version with this syntax

javascript
(async () => {
  //...
  const dbName="mydbname"
  const storeName="store0"
  const version = 1

const db = await openDB(dbName, version, {
    upgrade(db, oldVersion, newVersion, transaction) {
      switch (oldVersion) {
        case 0:
          // a store introduced in version 1
          db.createObjectStore('store1')
        case 1:
          // delete the old store in version 2, create a new one
          db.createObjectStore('store2', { keyPath: 'name' })
      }
      db.createObjectStore(storeName)
    }
  })
})()

Unique key

createObjectStore(), as you can see in case 1, takes a second parameter that sets the index key of the database. This is very handy when you store objects: put() calls then need no second parameter and can simply take the value (an object), with the key mapped to the property of the object that carries that name.

The index gives you a way to retrieve a value later on by that specific key, and it has to be unique (every item must have a different key).

A key can be set to auto-increment, so you do not have to keep track of it in the client code:

javascript
db.createObjectStore('notes', { autoIncrement: true })

Use auto-increment when your values do not already contain a unique key.


Checking whether an object store exists

You can check whether an object store already exists by calling the objectStoreNames() method:

javascript
const storeName="store1"

if (!db.objectStoreNames.contains(storeName)) {
  db.createObjectStore(storeName)
}

Deleting from IndexedDB

Deleting the database, an object store and data

Deleting a database

javascript
await deleteDB('mydb')

Deleting an object store

An object store can only be deleted inside the callback that runs when a database is opened, and that callback only fires if you ask for a version higher than the one currently installed:

javascript
const db = await openDB('dogsdb', 2, {
  upgrade(db, oldVersion, newVersion, transaction) {
    switch (oldVersion) {
      case 0:
        // a store introduced in version 1
        db.createObjectStore('store1')
      case 1:
        // delete the old store in version 2, create a new one
        db.deleteObjectStore('store1')
        db.createObjectStore('store2')
    }
  }
})

To delete data inside an object store, use a transaction

javascript
const key = 232

const db = await openDB(/*...*/)
const tx = await db.transaction('store', 'readwrite')
const store = await tx.objectStore('store')
await store.delete(key)
await tx.complete

JavaScript

Damien Flandrin Web developer since 2010, creator of Gekkode and Email Impact. Every article is tested on a real project before publication. Contact
Newsletter

New tests, tutorials and projects, by e-mail.

Reproducible tests, versioned code, dated results. Never any spam.