[DE] 📱 Überblick über Technologien zur Entwicklung von Progressive Web Apps #3 | IndexedDB
Um Datensätze auf dem Client speichern zu können gibt es verschiedene Optionen. Da wären Cookies, der localstorage, der Session Storage und die IndexedDB. In diesem Teil möchte ich auf die IndexedDB eingehen und erläutern, weswegen WebSQL nicht in der Liste aufgeführt wird.
Dies ist Teil 3 der Serie "Überblick über Technologien zur Entwicklung von Progressive Web Apps". Du hast Teil 1 noch nicht gelesen? Dann klicke hier.
IndexedDB
Die IndexedDB, oder auch Indexed Database API, ist eine Datenbankschnittstelle zur Speicherung von Objekten. Im Gegensatz zu herkömmlichen relationalen Datenbanken wie MySQL werden nicht einzelne Spalten, sondern ganze (JavaScript-)Objekte gespeichert. Die darunter befindliche Datenbank arbeitet transaktional. Das bedeutet: jede Aktion ist eine Transaktion. Schlägt eine Transaktion fehl, schlagen alle Transaktionen fehl. Die Objekte selbst werden pro Domain gespeichert (Stichwort: "Same-Origin-Policy"), was zur Folge hat, dass zwischen verschiedenen Webseiten / Apps kein Datentransfer stattfinden kann. Namensgebend benutzt die IndexedDB Indizes, nach welchen die einzelnen Datensätze angefordert werden können. Des Weiteren arbeitet die IndexedDB eigenständig im Browser, womit ungeachtet des Nutzers Netzwerkverbindung auf die Datensätze zugegriffen werden kann. Dieser Zugriff umfasst sowohl Lese-, als auch Schreibzugriff. Die IndexedDB ersetzt die veraltete Schnittstelle WebSQL.
WebSQL
WebSQL ist wie die IndexedDB eine Schnittstelle zur Speicherung von Daten. Sie wird mit einer Unterart von SQL (Sqlite3) angesprochen. Im Gegensatz zur IndexedDB ist diese allerdings veraltet und wird in weitaus weniger Browsern unterstützt. So können im Grunde nur der Chrome und die Safaris mit WebSQL arbeiten.
Programmierung
Genug Theorie, kommen zur zum spaßigen Teil: der Programmierung.
Eine Datenbankverbindung erstellen
Der erste Schritt ist eine Datenbankverbindung zu öffnen. Diese wird mit dem Namen der Datenbank und einer Versionsnummer geöffnet.
const openDatabase = (name, version = 1) => new Promise((resolve, reject) => {
const request = window.indexedDB.open(name, version);
request.onerror = function(event) {
reject(event.target.error);
};
request.onsuccess = function(event) {
const db = event.target.result;
resolve(db);
};
});
Wie an dem Code Beispiel deutlich wird, arbeitet die IndexedDB an jeder stelle asynchron. Dies hat den netten Nebeneffekt, dass das der Mainthread nicht blockiert wird.
Versionierung & Object Store
Was wir in unserer Funktion openDatabase
noch nicht implementiert haben, ist die Erstellung eines oder mehrerer Object Stores
. Diese sind mit Tabellen in relationalen Datenbanken zu vergleichen. Sie beinhalten später alle Objekte.
const openDatabase = (name, version = 1) => new Promise((resolve, reject) => {
// ...
// const oldVersion = event.oldVersion;
request.onupgradeneeded = function(event) {
const db = event.target.result;
db.createObjectStore('posts', { keyPath: 'post_id' });
};
});
Im oberen Codebeispiel wird der Object Store posts
und in ihm der eindeutige Schlüssel post_id
erstellt, sobald onupgradeneeded
aufgerufen wird. Dies ist der Fall, wenn die Versionsnummer beim öffnen der Datenbank mit window.indexedDB.open
erhöht wird.
Einfügen von Datensätzen
Ist die Datenbank geöffnet und alle genötigten Object Stores erstellt, können Datensätze eingetragen werden.
const insertPosts = async () => {
try {
const db = await openDatabase('steemit-db', 1);
const transaction = db.transaction('posts', 'readwrite');
const store = transaction.objectStore('posts');
const posts = [
{ post_id: 123, post_author: '@sidem', slug: 'in-15-years-zg1hbmlh-thlcx' },
{ post_id: 321, post_author: '@neutronenkind', slug: 'design-critique-service' },
];
posts.forEach(post => store.add(post));
console.info(`${posts.length} Posts eingefügt`);
} catch (error) {
console.error(`Datenbankfehler: ${error}`);
}
}
Zu sehen ist der typische Ablauf: erstellen einer Transaktion, öffnen des Stores und eintragen der Daten in diesen. Würden wir diesen Code mehr als einmal ausführen, würden wir in der Konsole Fehlermeldungen erhalten. Das hat den Hintergrund, dass sich die Post-IDs nicht ändern und diese als eindeutige Schlüssel hinterlegt sind.
Lesen von Daten
Das Lesen von Datensätzen erfolgt in der Regel mit der Hilfe eines Cursors. Diesen kann man sich vorstellen, wie einen Zeiger auf die aktuelle Position in einer Liste. Man kann diesen Zeiger vor und zurück schieben, um so alle Elemente betrachten zu können.
const readPosts = async () => {
try {
const db = await openDatabase('steemit-db', 1);
const transaction = db.transaction('posts', 'readwrite');
const store = transaction.objectStore('posts');
const request = store.openCursor();
request.onsuccess = function(event) {
const cursor = event.target.result;
if (!cursor) {
return
}
console.table(cursor.value);
cursor.continue();
}
} catch (error) {
console.error(`Datenbankfehler: ${error}`);
}
}
So werden nun alle unsere eingetragenen Posts nacheinander in einer Tabelle ausgegeben.
Indizes
Möchten wir unsere Posts nun zum Beispiel nach dem Autor gefiltert ausgeben lassen, müssen wir einen Index erstellen. Den dafür benötigten Code, schreiben wir erneut in unsere Funktion openDatabase
.
const openDatabase = (name, version = 1) => new Promise((resolve, reject) => {
// ...
// const oldVersion = event.oldVersion;
request.onupgradeneeded = function(event) {
const db = event.target.result;
if (!db.objectStoreNames.contains('posts')) {
const store = db.createObjectStore('posts', { keyPath: 'post_id' });
store.createIndex('post_author_idx', 'post_author', { unique: false });
}
};
});
Wenn wir nun openDatabase
mit einer höheren Versionsnummer als zuvor aufrufen, wird der Index erstellt und wir sind in der Lage unsere Posts nach dem Autoren gefiltert abzufragen.
const readPostsByAuthor = async (author) => {
try {
const db = await openDatabase('steemit-db', 2);
db.transaction('posts', 'readwrite')
.objectStore('posts')
.index('post_author_idx')
.openCursor(author)
.onsuccess = function(event) {
const cursor = event.target.result;
if (!cursor) {
return
}
console.table(cursor.value);
cursor.continue();
}
} catch (error) {
console.error(`Datenbankfehler: ${error}`);
}
}
Hier auch neu zu sehen ist die verkürzte Schreibweise, indem man die verschiedenen Methoden aneinander reiht.
Löschen
Möchten wir alle unsere Posts löschen nehmen wir einfach die Funktion clear
auf Ebene der Transaktionen.
const deleteAllPosts = async () => {
try {
const db = await openDatabase('steemit-db', 2);
db.transaction('posts', 'readwrite')
.objectStore('posts')
.clear()
.onsuccess = function(event) {
console.info('Alle Posts gelöscht');
}
} catch (error) {
console.error(`Datenbankfehler: ${error}`);
}
}
Unterstützung
Obwohl die IndexedDB in der Vergangenheit aufgrund mangelnder Implementierung im Safari in der Kritik stand wird sie heute in allen aktuellen Browsern unterstützt. Somit ist sie neben dem Service Worker eine der wichtigsten Schnittstellen für Progressive Web Apps.
Die folgenden Posts
In den folgenden Posts der Serie "Überblick über Technologien zur Entwicklung von Progressive Web Apps" werde ich auf folgende Technologien eingehen:
- Service Worker API inkl. CacheStorage & SyncManager ✔
- IndexedDB ✔
- Notification & Push API
- Payment Request API
- Credential Management API
- Web Share API & Web Share Target API
- Media Session API
Dabei werde ich mich maßgeblich an dem Buch Building Progressive Web Apps von Tal Ater orientieren.
Du hast Fragen, Änderungsvorschläge oder Wünsche? Lass es mich in den Kommentaren wissen ;)
In dem Sinne, frohes Coden.
(() => {
const colors = [
'001f3f', '0074d9', '7fdbff', '39cccc',
'3d9970', '2ecc40', '01ff70', 'ffdc00',
'ff851b', 'ff4136', '85144b', 'f012be',
];
const contents = ['%cI', '%c❤', '%cweb', '%cdev'];
const options = Array.from(
new Array(contents.length),
() => `
color:#${colors[Math.floor(Math.random() * colors.length)]};
font-size:64px;
`
);
console.log.apply(console, [contents.join('')].concat(options));
})();
Richtig guter Artikel, kommt bisher nicht oft vor dass ich bei SteemIt wirklich was mitnehme - danke
Danke dir :)
Ich stelle mir gerade die Frage, ob das positives oder negatives Feedback ist 😁
Äußerst positiv natürlich 😉
Danke für diesen sehr guten Artikel. In unserer Firma beschäftigen wir uns gerade ebenfalls mit der Thematik Offline-Funktionalität von Webapplikationen. Wir verwenden ebenfalls die IndexedDB. Dank deinem Artikel kann ich mehr mit dem Thema anfangen, weiter so!
Freut mich, dass er dir gefällt!
Wenn du damit beruflich zu tun hast, schau dir am Besten gleich noch die anderen Teile an. Die beschäftigen sich ebenfalls mit der Thematik ;)
Danke für den Hinweis, dann werde ich mir diese Teile demnächst auch ansehen :)