Indexed DB, die neue HTML5-Datenbank im Browser. Teil 1: ein kurzer Überblick

Achtung: dieser Beitrag ist alt! Es kann gut sein, dass seine Inhalte nicht mehr aktuell sind.

Logo für HTML5 Offline-Technologien

Die Indexed Database API (kurz Indexed DB) nimmt in der Web-Plattform die Rolle der dicken Datenbank ein. Während Web Storage als Cookie-Ersatz eingeplant ist und nicht als zuverlässiger Datenspeicher für große Applikationen taugt, ist Indexed DB genau dafür gedacht. Anders als bei Web Storage kann Indexed DB neben Strings auch Zahlen, Objekte und Arrays persistent speichern und selbst Blobs stellen kein Problem dar. Darüber hinaus können die gespeicherten Inhalte einfach aus der Datenbank herausgepickt werden und über Teile der Daten zu iterieren ist auch möglich – wie es bei einer richtigen Datenbank eben sein sollte. Damit stellt diese API einen wichtigen Mosaikstein im HTML5-Universum dar, denn einen anderen großkalibrigen Datenspeicher für offlinefähige Webapps wird es so bald nicht nicht browserübergreifend geben.

Nachdem ich fast zwei Wochen lang nichts weiter getan habe, als in den Spezifikationen und Implementierungen von Indexed DB herumzustochern, wird es Zeit, Bericht zu erstatten. Dieser erste Teil behandelt das Wesen der API gemäß der Spezifikationen. Das hier durchexerzierte Beispiel sollte aus dem Stand in aktuellen Chrome- und Firefox-Versionen sowie im Internet Explorer 10 funktionieren – wie man Indexed DB auch allen anderen Browserfamilien beibringt und was es an Fallstricken und Tools gibt, folgt im zweiten Teil.

Wieso Indexed DB?

IndexedDB ist eine NoSQL-Datenbank, ähnlich wie die in der Open-Source-Welt bekannten CouchDB und MongoDB. Solche Datenbanken haben anders als z.B. MySQL keine festen Datenbanktabellen, sondern bestehen aus Ansammlungen von „Dokumenten“ (meist JSON-Objekte) als Datensätzen. Innerhalb einer solchen Ansammlung können die einzelnen Datensätze bei Bedarf unterschiedlich aufgebaut sein, was mit einer starren Tabellenstruktur wie in den bekannten SQL-Datenbanken nicht ohne weiteres möglich ist. Der Preis für diese Flexibilität ist natürlich, dass die Applikation, die mit der Datenbank arbeitet, für alle unterschiedlich aufgebauten Dokumente gerüstet sein muss – die Datenbank wird im Vergleich zu SQL also konzeptionell vereinfacht, die Applikation potenziell komplexer. Behauptungen, nach denen das eine Modell skalierbarer oder performanter sei als das andere, dürften für die Arbeit im Webbrowser wohl erst mal von untergeordneter Bedeutung sein.

Obwohl NoSQL zur Zeit recht hip ist (in dem Rahmen, in dem Datenbanken hip sein können), ist es dennoch im Vergleich zu SQL die eher ungewöhnliche Lösung. Warum kommt gerade die in unsere Browser? Es gab mit Web SQL Database mal einen Ansatz, SQL-Datenbanken im Rahmen von HTML5 zu standardisieren, was aber 2010 gescheitert ist. Alle Browserhersteller, die an Web SQL interessiert waren, haben als Basis ihrer Umsetzung SQLite gewählt, ein bewährtes Open-Source-Datenbanksystem. In Folge traten auf dem Weg zur Standardisierung zwei Probleme auf:

  1. Hätte ein Browserhersteller ein anderes System als SQLite als Datenbank-Backend gewählt, wäre dessen Subset der SQL-Syntax nicht das gleiche gewesen, wie das der anderen Browser und die Implementierungen wären nicht kompatibel gewesen
  2. Solange nicht zwei voneinander unabhängige Implementierungen einer Technologie vorliegen, kann diese nicht zum Webstandard erhoben werden

Aus dieser Zwickmühle gab es nur einen Ausweg: hinfort mit Web SQL, her mit einer von Grund auf neuen Lösung. Die Spezifikationen für Indexed DB beschreiben ein komplett eigenes NoSQL-System, von der Browser-API bis hin zu den Algorithmen der einzelnen Datenbank-Operationen. Jeder Browserhersteller kann anhand dessen seine eigene Implementierung bauen, die dann trotzdem kompatibel zur Konkurrenz ist und auf dem Weg zum Webstandard nicht stört.

Hallo Welt mit Indexed DB I: Datenbank und Object Stores

Die API von Indexed DB in ihrer Gesamtheit ist recht komplex, weswegen wir uns in diesem Post auf das Durchexerzieren eines einfaches Schreiben-Lesen-Löschen-Beispiels beschränken, bei dem wir lediglich die Namen toller Browser-Features in einer Datenbank ablegen. Wie jede NoSQL-Datenbank hat auch Indexed DB seine ganz eigene Terminologie. Diese wird in den Spezifikationen umfassend erklärt; die wichtigsten Begriffe sind die Folgenden:

  • Datenbanken enthalten Object Stores
  • Object Stores entsprechen Datenbank-Tabellen und enthalten Datensätze
  • Ein Datensatz ist ein Key mit einem dazugehörigen Wert
  • Ein Key kann ein String eine Zahl oder ein Array sein und identifiziert einen Datensatz
  • Werte können alles mögliche sein: Strings, Zahlen, Objekte, Arrays, Blobs …
  • Transaktion sind ein Set gemeinsam durchgeführter Operationen
  • Cursor sind ein Iterationsmechanismus für größere Mengen von Datensätzen

Um etwas mit Indexed DB zu machen, braucht es zunächst eine Datenbank. Eine solche lässt sich ganz einfach per window.indexedDB.open() öffnen bzw. erzeugen:

// Datenbank anlegen
var request = indexedDB.open('html5', 1);

Die Datenbanken sind nach Origin (Kombination aus Host, Protokoll und Port einer Webapp) getrennt, d.h. foo.com kann nicht die Datenbank von www.bar.de öffnen.

Das erste Funktionsargument von indexedDB.open() ist der Name der Datenbank, das zweite die Versionsnummer. Diese Nummer wird für die interne Versionierung der Webapp verwendet – wann immer man Änderungen an der Struktur der DB vornehmen möchte, ändert man diese Zahl. Diese Änderung hat dann Auswirkung auf die Events der Datenbank-Anlege-Operation. Wie der obrige Code mit seinem var request bereits erahnen lässt, wird in der Zeile mit indexedDB.open() nicht wirklich die Datenbank angelegt, sondern es wird ein entsprechender asynchroner Request gestartet. Ist dieser abgeschlossen, feuern die folgenden Events auf dem request-Objekt:

  1. error wenn etwas beim Anlegen der Datenbank schiefgelaufen ist
  2. upgradeneeded wenn die Datenbank-Version sich geändert hat oder die Datenbank erstmals angelegt wurde
  3. success wenn die Datenbank geöffnet wurde (nach einem eventuellen upgradeneeded-Event)

Das upgradeneeded-Event ist von besonderer Bedeutung, denn nur im Event Handler von upgradeneeded können Änderungen an der Datenbankstruktur durchgeführt werden – versucht man z.B. einen neuen Object Store außerhalb von upgradeneeded anzulegen, setzt es eine Exception.

Beim ersten Aufruf unseres Beispiels legt der Browser die Datenbank „html5“ neu an, d.h. das upgradeneeded-Event feuert. Wir nutzen dies, um in der Datenbank einen Object Store anzulegen, der unsere liebsten HTML-Features enthalten wird:

// Änderungs/Erzeugungs-Event
request.onupgradeneeded = function(){
  console.log('Datenbank angelegt');
  var db = this.result;
  if(!db.objectStoreNames.contains('features')){
    store = db.createObjectStore('features', {
      keyPath: 'key',
      autoIncrement: true
    });
  }
};

Mit der createObjectStore()-Methode der frisch angelegten Datenbank (this.result im Event Handler) lassen sich neue Stores anlegen. Als Argumente werden ein Name sowie ein Konfigurations-Objekt übergeben, das in unserem Fall nur die Durchnummerierung der Datensätze festlegt. Hier entscheiden wir uns für automatisch generierte fortlaufende Nummern (autoIncrement: true) im Feld key unserer Einträge (keyPath: 'key').

Nach dem upgradeneeded-Event (oder sofort, wenn es keine DB-Änderungen gab) feuert das success-Event, sofern sich die Datenbank fehlerfrei öffnen ließ:

// Öffnungs-Event (feuert nach upgradeneeded)
request.onsuccess = function(){
  console.log('Datenbank geöffnet');
  var db = this.result;
  ...
}

Ab hier können wir mit Indexed DB in die Vollen gehen und nach Herzenslust Datensätze speichern, auslesen und löschen.

Hallo Welt mit Indexed DB II: Operationen und Transaktionen

Als Beispiel-Datensatz nehmen wir das denkbar einfachste JavaScript-Objekt:

// Zu speichernder Datensatz
var item = { title: 'Web Storage' };

Um mit Datensätzen in Indexed DB zu arbeiten, braucht es immer die gleichen vier Schritte:

  1. Transaktions-Objekt erstellen
  2. Object Store(s) auswählen
  3. Operation(en) starten (put, delete usw.)
  4. Events abwarten

Ein Transaktions-Objekt erhält man, indem man db.transaction() mit den Namen der betroffenen Object Stores sowie den gewünschten Berechtigungen (readonly oder readwrite) füttert:

// Speicher-Transaktion
var trans = db.transaction(['features'], 'readwrite');

Ab dann ist alles weitere ganz einfach: aus dem Transaktions-Objekt die jeweiligen Object Stores herauspicken (trans.objectStore('storeName')), die gewünschten Operationen veranlassen und auf das success-Event warten:

var store = trans.objectStore('features')
var request = store.put(item); // `item` in dem Store ablegen

// Erfolgs-Event
request.onsuccess = function(evt){
  console.log('Eintrag ' + evt.target.result + ' gespeichert');
  ...
};

Das gleiche Prinzip greift, wenn Einträge aus eine Object Store ausgelesen werden sollen. Im einfachsten Fall, wenn nur ein Datensatz mit bekanntem Key ausgelesen werden soll, braucht es nur ein store.get(42) um z.B. den Eintrag mit dem Key 42 auszulesen.

Wenn es ein bisschen mehr sein darf, werden Cursor und Key Ranges benötigt. Nachdem die Auslese-Transaktion eröffnet und der Object Store gewählt wurde …

var trans = db.transaction(['features'], 'readonly');
var store = trans.objectStore('features');

… muss Indexed DB mitgeteilt werden, für welchen Daten-Ausschnitt wir uns interessieren. Key Ranges sind hier das Mittel der Wahl. Mit einer der Methoden von window.IDBKeyRange lassen sich verschiedene Objekte erzeugen, die solche Ausschnitte beschreiben. Diese Objekte können zwei fixe Grenzwerte haben („alle Einträge von 23 bis 42“) oder nach oben oder unten offen sein (z.B. „alle Einträge ab 42“). Beispiele:

  • IDBKeyRange.lowerBound(0) für alle Einträge von 0 bis Ende
  • IDBKeyRange.upperBound(10) für alle Einträge bis 10
  • IDBKeyRange.only(23, 42) für alle Einträge zwischen 23 bis 42

Diese Grenzwerte beziehen sich in unserem Fall auf den Key der Datensätze; möchte man anhand anderer Felder durch die Datensätze rattern, kombiniert man Key Ranges mit Indizes. In jedem Fall öffnet man mit openCursor()-Methode von Store oder Index einen Cursor (ggf. mit Richtungsangabe), dessen success-Event für jeden Datensatz einmal feuert. In unserem Fall machen wir es uns so einfach wie möglich:

// Cursor für alle Einträge von 0 bis zum Ende
var range = IDBKeyRange.lowerBound(0);
var cursorRequest = store.openCursor(range);

// Wird für jeden gefundenen Datensatz aufgerufen... und einmal extra
cursorRequest.onsuccess = function(evt){
  var result = evt.target.result;
  ...
};

Das success-Event feuert für jeden Datensatz sowie wenn alle Datensätze abgearbeitet wurden nochmal. Für gefundene Datensätze ist die target.result-Eigenschaft des Event-Objekts der gefundene Eintrag, beim letzten Event null. Mit dem Datensätzen kann dann die gewünschte Arbeit verrichtet werden und am Ende der Cursor mittels target.result.continue() zum nächsten Eintrag bewegt werden.

In unserem simplem Beispiel machen wir nichts weiter, als den Eintrag in die Konsole zu schreiben und ihn dann gleich wieder zu löschen:

if(result){
  console.log('Eintrag gefunden:', result.value);

  // Eintrag wieder löschen
  var trans = db.transaction(['features'], 'readwrite');
  var store = trans.objectStore('features');
  var key = result.value.key;
  var request = store.delete(key);
  request.onsuccess = function(evt){
    console.log('Eintrag ' + key + ' gelöscht');
  }
  
  // Cursor zum nächsten Eintrag bewegen
  result.continue();

}

Und fertig! Damit haben wir nur ein sehr sehr simples Beispiel (Demo, Gist) geschaffen, aber eines, das zumindest die wichtigsten Eckpunkte von Indexed DB demonstriert. Und so richtig schwer war es ja nicht – Indexed DB folgt dem löblichen Trend moderner HTML5-APIs, möglichst einfache Schnittstellen für die Webapps von morgen bereitzustellen. Bleibt nur die ewige Frage nach der Browserunterstützung …

In welchen Browser funktioniert Indexed DB?

Grundsätzlich ist diese Frage leicht zu beantworten: in Chrome, Firefox und IE 10 gibt es Unterstützung, ansonsten sieht es finster aus. In allen drei Browsern ist die API mittlerweile ohne Vendor-Prefix benutzbar und es wird allenthalben davon ausgegangen, dass das auch so bleibt, d.h. dass die aktuelle Spezifikationen den Stand des späteren Standards wiederspiegeln.

Problematisch sind alle Browser, die nicht Chrome, Firefox oder IE 10 sind, wobei „problematisch“ gewohnt relativ ist. Zum einen lässt sich Indexed DB per Polyfill für die meisten Browser nachrüsten, zum anderen gibt es durchaus, anders als es Caniuse (noch) angibt, Indexed DB in Android-Browsern ab Version 4 – wenn auch in einer Version aus der frühen Kreidezeit. Alles kein Problem, alles zu fixen, alles in Teil 2 dieses Artikels beschrieben.

Kommentare (15)

  1. Peter Majmesku

    21. Januar 2013, 16:55 Uhr

    Hi,

    danke für den Artikel! Frage: die Datenbank wird im Browser gelöscht, sobald der Benutzer den Browser schließt? Wenn ja: gibt es einen "Hook" mit dem man vor dem Beenden des Browsers noch schnell in die DB des Servers speichern kann?

  2. Peter Kröner

    21. Januar 2013, 17:22 Uhr

    Die Datenbank bleibt da, bis sie explizit vom Nutzer gekillt wird, in etwa wie bei Cookies.

  3. trenc

    22. Januar 2013, 09:46 Uhr

    Wenn ja: gibt es einen "Hook" mit dem man vor dem Beenden des Browsers noch schnell in die DB des Servers speichern kann?

    Wenn mich nicht alles täuscht, sollte das mit dem window.onbeforeunload funktionieren, wenn es denn vom Browser unterstützt wird:

    window.onbeforeunload = function() {
     // save here
    }
  4. Dominik

    22. Januar 2013, 11:22 Uhr

    Leider ist onbeforeunload nicht wirklich zu gebrauchen in Chrome. Es gibt ein paar Ideen in dem Bug, wie eine Alternative basteln kann um Daten zu speichern, aber nichts wirklich schönes.

    Wie es bei anderen Browsern aussieht weiß ich leider nicht.

  5. Lars Schweisthal

    22. Januar 2013, 13:30 Uhr

    Erstmal danke für den ausführlichen Einblick.

    Was mich dabei noch interessiert: Handelt es sich hierbei um eine reine Client-DB? Sprich, wenn ich als Webseitenbetreiber an die Daten kommen möchte, müsste ich noch einen Event basteln, der die DB an den Server überträgt? (Wie genau wird die DB überhaupt gespeichert?)

    Dann wäre es ja quasi das gleiche wie Web Storage, nur halt für größere Datenmengen.

  6. Peter Kröner

    22. Januar 2013, 13:59 Uhr

    Was mich dabei noch interessiert: Handelt es sich hierbei um eine reine Client-DB? Sprich, wenn ich als Webseitenbetreiber an die Daten kommen möchte, müsste ich noch einen Event basteln, der die DB an den Server überträgt? [...] Dann wäre es ja quasi das gleiche wie Web Storage, nur halt für größere Datenmengen.

    Genau so ist das. Weitere Unterschiede zu Web Storage wären noch, dass Indexed DB eben asynchron ist und das Speichern von allen möglichen Datentypen zulässt, nicht nur von Strings.

    Wie genau wird die DB überhaupt gespeichert?

    Das ist den Browserherstellern überlassen. Es wird nicht vorgeschrieben, wie etwas unter der Haube zu funktionieren hat, wichtig ist nur, dass die API das macht, was man erwarten würde. Zitat Spezifikationen:

    User agents may implement algorithms given in this specification in any way desired, so long as the end result is indistinguishable from the result that would be obtained by the specification's algorithms.

    Im Firefox sind die Indexed DB-Datenbanken beispielsweise in einer ganz normalen SQLite-Datenbank gespeichert. Die beiden anderen verwenden jeweils hauseigene Speichertechniken: Extensible Storage Engine im IE, LevelDB in Chrome.

  7. Timo Stolz

    6. Juni 2013, 11:30 Uhr

    Herzlichen Dank für den gelungenen Überblick! Ich finde grossartig, wie Sie auf alles eingegangen sind, was mir nach Stunden auf Stackoverflow immer mehr zu einem Nebel im Kopf geworden ist. Jetzt sehe ich klarer!

  8. Christian Hanne

    25. Juni 2013, 16:36 Uhr

    Danke für den Artikel, schöne Zusammenfassung.

    Als Programmierer freue ich mich über die neuen Möglichkeiten, die sich mir mit Indexed DB bieten. Als User beschleicht mich allerdings ein ungutes Gefühl, wenn Webseiten in Zukunft noch mehr Daten auf meinem Rechner speichern können...

  9. Peter Kröner

    28. Juni 2013, 23:54 Uhr

    Schlimmer als wenn die Webseiten deine Daten auf ihren eigenen, unsicheren, an die NSA angebundenen Servern speichern wird es schon nicht werden :)

  10. ralphi

    11. Juli 2013, 02:38 Uhr

    Hi Peter,

    tolles tutorial ;-)
    Nach wie vor, bin ich dennoch auf der Suche nach einer Offlinedatenbank (IE10 WinRT), in der ich viele Tabellen (natürlich mit mehreren Spalten), mit WHERE Abfragen, Updaten etc. bearbeiten kann. Das ganze sollte dann, bei WAN-Verbindung mit einem Server replizieren. Das ganze würde prima mit Chrome, Safari und Android-browser mit:

    function initDb() {
    var shortName = 'testDb';
    var version = '1.0';
    var displayName = 'test db';
    var maxSize = 1048576; // in bytes
    mydb = window.openDatabase(shortName, version, displayName, maxSize);
    }

    funktionieren, nur leider haben wir ‚nur’ Win RT Tablet’s.
    Jetzt stellt sich die Frage, ob sich schon jemand die Mühe gemacht und eine SQL-Interface für Indexed DB geschrieben hat.
    Ich denke um eine Tabelle darzustellen, muss man Anz. Spalten = Anz. Object stores mit gleichem key. Für eine SELECT WHERE, einen guten Suchalgorithmus basteln.
    Kennst Du eine Web-SQL Lösung für IE10?
    Grüße aus LA
    ralphi

  11. Peter Kröner

    11. Juli 2013, 20:33 Uhr

    Kennst Du eine Web-SQL Lösung für IE10?

    Mir ist diesbezüglich nichts bekannt. Ich würde damit auch eher vorsichtig sein, denn Web SQL ist kein Webstandard und könnte theoretisch jeden Moment aus den Browsern fliegen oder inkompatibel umgebaut werden. Das ist nicht sehr wahrscheinlich, aber auch nicht auszuschließen. Im Browser ist einfach aktuell NoSQL der Weg der Wahl.

    Wenn du Inspiration suchst, wie du mit Indexed DB deine eigene SQL-Abstraktionsschicht bauen kannst, würde ich mir mal ansehen wie es der Firefox macht. Dort ist die IDB-Implementierung mittels SQLite umgesetzt.

  12. Micha

    31. Januar 2014, 09:41 Uhr

    Hallo Peter,

    für mich bleibt immer noch die Frage offen, wie die Daten mit der Cloud synchronisiert werden können. Käme dafür der Server, auf dem die Webseite liegt in Frage?

    Wenn das möglich ist, gibt es dann Implementierungen, um Inhalte auf einem anderen Gerät im Browser in Echtzeit zu aktualisieren?
    Z.B. User A auf einem PC ändert Inhalte mit Firefox in der Datenbank, und sobald mit dem Server synchronisiert, ändert sich auch der Seiteninhalt, der auf den geänderten Daten in der Datenbank basiert, im Chrome Browser des Tablets von User B, ohne das User B aktualisieren muss.

    Grüße,
    Micha

  13. Peter Kröner

    1. Februar 2014, 00:14 Uhr

    Alles kein Problem, machen die Produkte aus dem Hause Google Docs ja auch schon länger. Es ist nur nicht ganz trivial immer alles synchron zu halten, aber die Werkzeuge … lokale Datenbanken, XHR und/oder Web Sockets und entsprechende Serverkomponenten – sind da.

  14. Rolf

    12. Februar 2014, 23:19 Uhr

    Interessanter Artikel, vielen Dank!!!!

    Die neuen Features moderner Browser begeistern mich und so schreibe ich auch darüber ;)
    ZZ bin ich an einer linearen Datenbank (proprietär) die lokal in den Browser eingespielt wird, in der DB sind Objekte gespeichert, die verschiedene Content-Types enthalten wie image/gif, video/webm, audio/mp3 usw. sowie auch Texte.

    Allerdings muss hier der Benutzer aktiv werden, möglicherweise gibt es jedoch eine Lösung ähnlich IndexDB?

    Viele Grüße, Rolf

  15. trenc

    13. Februar 2014, 10:41 Uhr

    Du suchst vermutlich localForage: https://github.com/mozilla/localForage

Die Kommentarfunktion ist seit Juli 2014 deaktiviert, weil sie zu sehr von Suchmaschinenoptimierern für manuellen Spam mißbraucht wurde. Wenn du Anmerkungen oder Fragen zum Artikel hast, schreib mir eine E-Mail oder melde dich via Twitter.

Folgt mir!

Kauft mein Zeug!

Cover der HTML5-DVD
Infos auf html5-dvd.de, kaufen bei Amazon

Cover des HTML5-Buchs
Infos auf html5-buch.de, kaufen bei Amazon

Cover des CSS3-Buchs
Infos auf css3-buch.de, kaufen bei Amazon