IndexedDB lernen: Daten im Browser mit JavaScript speichern

IndexedDB lernen: Daten im Browser mit JavaScript speichern

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:

javascript
<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:

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

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

So legst du eine IndexedDB-Datenbank an

openDB() nutzen:

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

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:

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

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:

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

Daten aus einem Object Store lesen

Einen Eintrag aus einem Object Store holen: get()

javascript
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

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

Alle gespeicherten Werte holen

javascript
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

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

Daten in einem Object Store löschen

Dafür nutzen wir eine Transaktion:

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

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):

javascript
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

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:
          // 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:

javascript
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():

javascript
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

javascript
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:

javascript
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

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 Webentwickler seit 2010, Gründer von Gekkode und Email Impact. Jeder Artikel wird vor der Veröffentlichung an einem echten Projekt getestet. Kontakt
Newsletter

Neue Tests, Tutorials und Projekte, per E-Mail.

Reproduzierbare Tests, versionierter Code, datierte Ergebnisse. Niemals Spam.