
IndexedDB ist eine der Speicherlösungen, die im Lauf der Jahre in die Browser eingezogen sind.
Es handelt sich um eine Key-Value-Datenbank (eine NoSQL-Datenbank), die als die endgültige Lösung für die Datenspeicherung im Browser gilt.
Die API ist asynchron: Der Rest deines Programms läuft weiter, während gelesen oder geschrieben wird, was den Nutzern spürbar zugutekommt. Die Datenmenge ist im Prinzip unbegrenzt, ab einer bestimmten Schwelle fragt der Browser den Nutzer allerdings, ob die Seite mehr Daten ablegen darf.
Es gibt weitere Methoden, nämlich Cookies und den Web Storage (localStorage und sessionStorage). Local Storage und Session Storage haben den Nachteil, dass sie auf wenig Platz begrenzt sind: Die Browser geben pro Seite zwischen 2 MB und 10 MB frei.
Technisch kannst du pro Seite mehrere Datenbanken anlegen, üblicherweise legst du aber eine einzige an und darin mehrere Object Stores.
Eine Datenbank gehört zu genau einer Domain, keine andere Seite kommt an eine fremde IndexedDB heran.
Jeder Object Store enthält in der Regel eine Sammlung von Dingen, und zwar:
- Objects
- Arrays
- Strings
- Numbers
- Dates
Du kannst zum Beispiel einen Store für Beiträge halten und einen zweiten für Kommentare.
Ein Store enthält eine Reihe von Objekten mit jeweils einem eindeutigen Schlüssel, über den sich ein Objekt identifizieren lässt.
Geändert werden diese Objekte über Transaktionen: hinzufügen, ändern, löschen und über die enthaltenen Einträge iterieren.
Seit Promises mit ES6 kamen und die APIs nach und nach darauf umgestellt wurden, wirkt die IndexedDB-API etwas altmodisch.
Das ist nicht weiter schlimm, trotzdem nutze ich in allen folgenden Beispielen die Promise-Bibliothek IndexedDB von Jake Archibald, eine dünne Schicht über der IndexedDB-API, die die Handhabung deutlich angenehmer macht.
Dieselbe Bibliothek kommt auch in allen IndexedDB-Beispielen auf der Seite Google Developers zum Einsatz
Eine IndexedDB-Datenbank anlegen
Am einfachsten geht das über unpkg, eingebunden im Head der Seite:
<script type="module">
import { openDB, deleteDB } from 'https://unpkg.com/idb?module'
</script>Bevor du die IndexedDB-API nutzt, prüfe immer, ob der Browser sie unterstützt. Die Unterstützung ist zwar breit, aber du weißt nie, mit welchem Browser dein Nutzer unterwegs ist:
(async () => {
'use strict'
if (!('indexedDB' in window)) {
console.warn('IndexedDB not supported')
return
}
})()So legst du eine IndexedDB-Datenbank an
openDB() nutzen:
(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)
}
})
})()Die ersten beiden Parameter sind der Name der Datenbank und die Version. Der dritte, optionale Parameter ist ein Objekt mit einer Funktion, die nur dann aufgerufen wird, wenn die Versionsnummer höher ist als die der installierten Datenbank. In diesem Funktionsrumpf aktualisierst du die Struktur der Datenbank (Object Stores und Indizes).
Daten in einen Store schreiben
Daten beim Anlegen des Stores schreiben, den Store initialisieren
Zum Schreiben nutzt du die Methode put des Object Stores, den du vorher beim Anlegen mit db.createObjectStore() initialisieren musst.
Bei put ist der Wert das erste Argument und der Schlüssel das zweite. Der Grund: Wenn du beim Anlegen des Object Stores einen keyPath angibst, musst du den Schlüsselnamen nicht bei jedem put() mitgeben, sondern nur den Wert.
Damit wird store0 gleich beim Anlegen befüllt:
(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')
}
})
})()Daten in einen bereits angelegten Store schreiben, über Transaktionen
Um später Einträge hinzuzufügen, brauchst du eine Lese-/Schreib-Transaktion, die die Integrität der Datenbank sichert (schlägt eine Operation fehl, werden alle Operationen der Transaktion zurückgerollt und der Ausgangszustand wiederhergestellt).
Dafür nimmst du die Referenz auf das Objekt dbPromise aus dem Aufruf von openDB und schreibst:
(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
})()Daten aus einem Object Store lesen
Einen Eintrag aus einem Object Store holen: get()
const key = 'Hello again'
const item = await db.transaction(storeName).objectStore(storeName).get(key)Alle Einträge aus einem Object Store holen: getAll()
Alle gespeicherten Schlüssel holen
const items = await db.transaction(storeName).objectStore(storeName).getAllKeys()Alle gespeicherten Werte holen
const items = await db.transaction(storeName).objectStore(storeName).getAll()Daten aus IndexedDB löschen
Die Datenbank, einen Object Store und einzelne Daten löschen
Eine IndexedDB-Datenbank vollständig löschen
const dbName="mydbname"
await deleteDB(dbName)Daten in einem Object Store löschen
Dafür nutzen wir eine Transaktion:
(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
})()Von einer früheren Version der Datenbank migrieren
Der dritte (optionale) Parameter von openDB() ist ein Objekt, das eine Upgrade-Funktion enthalten kann. Sie wird nur aufgerufen, wenn die Versionsnummer höher ist als die der installierten Datenbank. In diesem Funktionsrumpf aktualisierst du die Struktur der Datenbank (Object Stores und Indizes):
const name="mydbname"
const version = 1
openDB(name, version, {
upgrade(db, oldVersion, newVersion, transaction) {
console.log(oldVersion)
}
})In diesem Callback kannst du prüfen, von welcher Version aus der Nutzer aktualisiert, und je nachdem unterschiedliche Schritte ausführen.
Eine Migration von einer früheren Datenbankversion schreibst du mit dieser 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:
// ein in Version 1 eingeführter Store
db.createObjectStore('store1')
case 1:
// den alten Store in Version 2 löschen, einen neuen anlegen
db.createObjectStore('store2', { keyPath: 'name' })
}
db.createObjectStore(storeName)
}
})
})()Eindeutiger Schlüssel
createObjectStore()nimmt, wie du im ersten Fall siehst, einen zweiten Parameter entgegen, der den Indexschlüssel der Datenbank angibt. Das ist beim Speichern von Objekten sehr praktisch: Die Aufrufe von put() brauchen kein zweites Argument mehr, sondern nur den Wert (ein Objekt), und der Schlüssel wird auf die gleichnamige Eigenschaft des Objekts abgebildet.
Über den Index holst du einen Wert später anhand dieses Schlüssels zurück, und er muss eindeutig sein (jeder Eintrag braucht einen eigenen Schlüssel).
Ein Schlüssel lässt sich automatisch hochzählen, dann musst du ihn im Client-Code nicht selbst verwalten:
db.createObjectStore('notes', { autoIncrement: true })Nutze das automatische Hochzählen, wenn deine Werte nicht ohnehin schon einen eindeutigen Schlüssel enthalten.
Prüfen, ob ein Object Store existiert
Ob ein Object Store bereits existiert, prüfst du über die Methode objectStoreNames():
const storeName="store1"
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName)
}Aus IndexedDB löschen
Die Datenbank, einen Object Store und einzelne Daten löschen
Eine Datenbank löschen
await deleteDB('mydb')Einen Object Store löschen
Ein Object Store lässt sich nur im Callback beim Öffnen einer Datenbank löschen, und dieses Callback läuft nur, wenn du eine höhere Version angibst als die gerade installierte:
const db = await openDB('dogsdb', 2, {
upgrade(db, oldVersion, newVersion, transaction) {
switch (oldVersion) {
case 0:
// ein in Version 1 eingeführter Store
db.createObjectStore('store1')
case 1:
// den alten Store in Version 2 löschen, einen neuen anlegen
db.deleteObjectStore('store1')
db.createObjectStore('store2')
}
}
})Daten in einem Object Store löschen: mit einer Transaktion
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

