
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:
<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:
(async () => {
'use strict'
if (!('indexedDB' in window)) {
console.warn('IndexedDB not supported')
return
}
})()How to create an IndexedDB database
Use openDB():
(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:
(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:
(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()
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
const items = await db.transaction(storeName).objectStore(storeName).getAllKeys()Getting every stored value
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
const dbName="mydbname"
await deleteDB(dbName)Deleting data inside an object store
We use a transaction:
(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):
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
(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:
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:
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
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:
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
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

