IndexedDB gebruiken: data opslaan in de browser met JavaScript

IndexedDB gebruiken: data opslaan in de browser met JavaScript

IndexedDB is een van de opslagoplossingen die in de loop der jaren in browsers zijn geïntroduceerd.

Het gaat om een key-valuedatabase (een noSQL-database) die geldt als dé oplossing voor gegevensopslag in de browser.

Het is een asynchrone API: de uitvoering van je code blokkeert de rest van je programma niet, wat de gebruikerservaring merkbaar ten goede komt. Ze kan een onbepaalde hoeveelheid data opslaan, al krijgt de gebruiker boven een bepaalde drempel de vraag of de site meer data mag bewaren.

Er bestaan andere methodes, de cookies en Web Storage ( localStorage en sessionStorage ). Local en session storage hebben het nadeel dat ze tot een kleine omvang beperkt blijven: browsers geven 2MB tot 10MB ruimte per site.

Technisch gezien kun je meerdere databases per site aanmaken, maar meestal maak je er één, en binnen die database maak je verschillende object stores aan.

Een database is privé per domein, zodat geen enkele andere site bij een IndexedDB-database van een ander domein kan.

Elke object store bevat doorgaans een verzameling dingen die je kunt gebruiken:

  • objects
  • arrays
  • strings
  • numbers
  • dates

Je kunt bijvoorbeeld een store hebben met berichten en een andere met reacties.

Een store bevat een aantal objecten met een unieke sleutel, die bepaalt hoe een object wordt geïdentificeerd.

Je bewerkt die objecten via transacties, met operaties om de elementen erin toe te voegen, te wijzigen, te verwijderen en te doorlopen.

Sinds Promises met ES6 hun intrede deden, en API’s daarna op promises overstapten, oogt de IndexedDB-API een beetje ouderwets.

Daar is niets mis mee, maar in alle voorbeelden die ik uitleg gebruik ik de promise-bibliotheek IndexedDB van Jake Archibald, een dun laagje over de IndexedDB-API dat het gebruik eenvoudiger maakt.

Deze bibliotheek wordt ook gebruikt in alle IndexedDB-voorbeelden op de site Google Developers


Een IndexedDB-database aanmaken

De eenvoudigste manier is unpkg, door dit in de head van de pagina te zetten:

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

Controleer voordat je de IndexedDB-API gebruikt altijd of de browser haar ondersteunt. Ze is breed beschikbaar, maar je weet nooit welke browser je gebruiker draait:

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

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

Hoe maak je een IndexedDB-database aan?

Gebruik 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)
    }
  })
})()

De eerste 2 parameters zijn de naam van de database en de versie. De derde parameter is optioneel: een object met een functie die alleen wordt aangeroepen als het versienummer hoger ligt dan de huidige versie van de geïnstalleerde database. In die functie werk je de structuur van de database bij (object stores en indexen).

Data toevoegen aan een store

Data toevoegen bij het aanmaken van de store, de store initialiseren

Om data toe te voegen gebruik je de methode put van de object store, maar die moeten we eerst initialiseren met db.createObjectStore() op het moment dat we haar aanmaken.

Bij put is de waarde het eerste argument en de sleutel het tweede. Dat komt doordat je, als je bij het aanmaken van de object store een keyPath opgeeft, de sleutelnaam niet bij elke put() hoeft mee te geven: je schrijft gewoon de waarde.

Dit vult store0 zodra we hem hebben aangemaakt:

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')
    }
  })
})()

Data toevoegen als de object store al bestaat, met transacties

Om later elementen toe te voegen maak je een lees-/schrijftransactie aan, die de integriteit van de database bewaakt (mislukt één operatie, dan worden alle operaties van de transactie teruggedraaid en keert de staat terug naar het begin).

Gebruik daarvoor een referentie naar het object dbPromise dat we van openDB hebben gekregen, en draai:

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
})()

Data uit een object store halen

Eén item uit een object store halen: get()

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

Alle items uit een object store halen: getAll()

Alle opgeslagen sleutels ophalen

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

Alle opgeslagen waarden ophalen

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

Data uit IndexedDB verwijderen

De database, een object store en data verwijderen

Een IndexedDB-database volledig verwijderen

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

Data in een object store verwijderen

Daarvoor gebruiken we een transactie:

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
})()

Migreren vanaf een vorige versie van een database

De derde (optionele) parameter van de functie openDB() is een object dat een upgradefunctie kan bevatten, die alleen wordt aangeroepen als het versienummer hoger ligt dan de geïnstalleerde databaseversie. In die functie werk je de structuur van de database bij (object stores en indexen):

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

In die callback kun je nagaan vanaf welke versie de gebruiker bijwerkt en daar de juiste operaties aan koppelen.

Migreren vanaf een vorige databaseversie doe je met deze syntaxis

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:
          // een store die in versie 1 is geïntroduceerd
          db.createObjectStore('store1')
        case 1:
          // de oude object store in versie 2 verwijderen en een nieuwe aanmaken
          db.createObjectStore('store2', { keyPath: 'name' })
      }
      db.createObjectStore(storeName)
    }
  })
})()

Unieke sleutel

createObjectStore()accepteert, zoals je in geval 1 ziet, een tweede parameter met de indexsleutel van de database. Dat is erg handig als je objecten opslaat: de aanroepen van put() hebben geen tweede parameter nodig, maar nemen gewoon de waarde (een object), en de sleutel wordt gekoppeld aan de eigenschap van het object met die naam.

De index geeft je een manier om een waarde later via die specifieke sleutel op te halen, en hij moet uniek zijn (elk element moet een andere sleutel hebben).

Een sleutel kan automatisch ophogen, zodat je hem niet in de clientcode hoeft bij te houden:

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

Gebruik automatisch ophogen als je waarden nog geen unieke sleutel bevatten.


Controleren of een object store bestaat

Je gaat na of een object store al bestaat door de methode objectStoreNames() aan te roepen:

javascript
const storeName="store1"

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

Verwijderen uit IndexedDB

De database, een object store en data verwijderen

Een database verwijderen

javascript
await deleteDB('mydb')

Een object store verwijderen

Een object store kun je alleen verwijderen in de callback bij het openen van een db, en die callback wordt alleen aangeroepen als je een hogere versie opgeeft dan de versie die nu geïnstalleerd is:

javascript
const db = await openDB('dogsdb', 2, {
  upgrade(db, oldVersion, newVersion, transaction) {
    switch (oldVersion) {
      case 0:
        // een store die in versie 1 is geïntroduceerd
        db.createObjectStore('store1')
      case 1:
        // de oude object store in versie 2 verwijderen en een nieuwe aanmaken
        db.deleteObjectStore('store1')
        db.createObjectStore('store2')
    }
  }
})

Data in een object store verwijderen doe je met een transactie

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 Webdeveloper sinds 2010, maker van Gekkode en Email Impact. Elk artikel wordt vóór publicatie getest op een echt project. Contact
Nieuwsbrief

Nieuwe tests, tutorials en projecten, per e-mail.

Reproduceerbare tests, geversioneerde code, gedateerde resultaten. Nooit spam.