
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:
<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:
(async () => {
'use strict'
if (!('indexedDB' in window)) {
console.warn('IndexedDB not supported')
return
}
})()Hoe maak je een IndexedDB-database aan?
Gebruik 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)
}
})
})()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:
(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:
(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()
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
const items = await db.transaction(storeName).objectStore(storeName).getAllKeys()Alle opgeslagen waarden ophalen
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
const dbName="mydbname"
await deleteDB(dbName)Data in een object store verwijderen
Daarvoor gebruiken we een transactie:
(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):
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
(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:
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:
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
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:
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
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

