// apis
IndexedDB-Tutorial: eine Notiz-App bauen, die offline funktioniert
Ein Schritt-fuer-Schritt-Tutorial zu IndexedDB. Datenbank oeffnen, Object Store erstellen, Datensaetze hinzufuegen und lesen, nach Index abfragen, Eintraege loeschen und Versionsupgrades verwalten, alles in einer kleinen Notiz-App, die im Browser laeuft.
Der Konzeptguide erklaert, was IndexedDB ist und wann man sie verwenden sollte. Dieses Tutorial baut eine kleine Notiz-Anwendung von Grund auf, damit Sie jeden Schritt sehen: Datenbank oeffnen, Store erstellen, Datensaetze schreiben, sie zuruecklesen, nach Index abfragen und Eintraege loeschen.
Alles laeuft im Browser. Keine Build-Tools, kein Server, keine Abhaengigkeiten. Kopieren Sie jeden Block in eine einzige HTML-Datei und oeffnen Sie sie.
Schritt 1: Datenbank oeffnen
Jede IndexedDB-Interaktion beginnt mit indexedDB.open(). Sie uebergeben einen Namen und eine Versionsnummer. Falls die Datenbank noch nicht existiert oder Ihre Version hoeher ist als die auf der Festplatte, feuert der Browser upgradeneeded.
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('notes-app', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('notes')) {
const store = db.createObjectStore('notes', {
keyPath: 'id',
autoIncrement: true,
});
store.createIndex('by_tag', 'tag', { unique: false });
store.createIndex('by_date', 'createdAt', { unique: false });
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
} Einige Dinge sind zu beachten. keyPath: 'id' sagt IndexedDB, welche Eigenschaft jedes Objekts der Primaerschluessel ist. autoIncrement: true laesst die Datenbank diesen Schluessel fuer Sie generieren. Die beiden createIndex-Aufrufe ermoeglichen es Ihnen spaeter, Notizen nach Tag oder Datum abzufragen, ohne jeden Datensatz zu durchsuchen.
Schritt 2: einen Datensatz hinzufuegen
Das Schreiben in IndexedDB erfolgt immer innerhalb einer Transaktion. Sie oeffnen eine auf dem benotigten Object Store, holen sich eine Referenz auf diesen Store und rufen add() oder put() auf.
async function addNote(db, text, tag) {
return new Promise((resolve, reject) => {
const tx = db.transaction('notes', 'readwrite');
const store = tx.objectStore('notes');
const record = {
text,
tag: tag || 'general',
createdAt: new Date().toISOString(),
};
const request = store.add(record);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
} add() wirft einen Fehler, wenn ein Datensatz mit demselben Schluessel bereits existiert. put() ueberschreibt stattdessen. Fuer neue Datensaetze ist add() sicherer, weil es versehentliche Duplikate abfaengt.
Schritt 3: alle Datensaetze lesen
Um alles in einem Store zu lesen, oeffnen Sie eine readonly-Transaktion und rufen getAll() auf:
async function getAllNotes(db) {
return new Promise((resolve, reject) => {
const tx = db.transaction('notes', 'readonly');
const store = tx.objectStore('notes');
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
} Wenn Sie nur einen Datensatz brauchen und seinen Schluessel kennen, verwenden Sie stattdessen store.get(key). Es gibt ein einzelnes Objekt statt eines Arrays zurueck.
Schritt 4: nach Index abfragen
Indizes ermoeglichen es, Datensaetze zu finden, ohne den gesamten Store zu durchsuchen. Um alle Notizen mit dem Tag "work" zu erhalten:
async function getNotesByTag(db, tag) {
return new Promise((resolve, reject) => {
const tx = db.transaction('notes', 'readonly');
const store = tx.objectStore('notes');
const index = store.index('by_tag');
const request = index.getAll(tag);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
} Sie koennen auch einen Key Range verwenden, um eine Spanne von Werten abzufragen. Zum Beispiel, um Notizen zu erhalten, die nach einem bestimmten Datum erstellt wurden:
const range = IDBKeyRange.lowerBound('2026-01-01T00:00:00.000Z');
const request = store.index('by_date').getAll(range); Die vier Range-Konstruktoren sind lowerBound, upperBound, bound (beide Enden) und only (exakte Uebereinstimmung). Jeder akzeptiert einen optionalen Boolean, um den Grenzwert auszuschliessen.
Schritt 5: einen Datensatz aktualisieren
Das Aktualisieren verwendet put(). Sie muessen die Schluessel-Eigenschaft (id in unserem Schema) einschliessen, damit IndexedDB weiss, welchen Datensatz es ueberschreiben soll:
async function updateNote(db, id, newText) {
return new Promise((resolve, reject) => {
const tx = db.transaction('notes', 'readwrite');
const store = tx.objectStore('notes');
const getReq = store.get(id);
getReq.onsuccess = () => {
const note = getReq.result;
if (!note) { reject(new Error('Not found')); return; }
note.text = newText;
const putReq = store.put(note);
putReq.onsuccess = () => resolve();
putReq.onerror = () => reject(putReq.error);
};
getReq.onerror = () => reject(getReq.error);
});
} Schritt 6: einen Datensatz loeschen
Rufen Sie store.delete(key) innerhalb einer readwrite-Transaktion auf:
async function deleteNote(db, id) {
return new Promise((resolve, reject) => {
const tx = db.transaction('notes', 'readwrite');
const store = tx.objectStore('notes');
const request = store.delete(id);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
} Um alle Datensaetze in einem Store zu loeschen, verwenden Sie stattdessen store.clear().
Schritt 7: Versionsupgrades verwalten
Wenn Sie einen neuen Index oder einen neuen Store hinzufuegen muessen, nachdem Benutzer bereits Version 1 haben, erhoehen Sie die Versionsnummer und behandeln Sie die Migration in onupgradeneeded:
request.onupgradeneeded = (event) => {
const db = event.target.result;
const oldVersion = event.oldVersion;
if (oldVersion < 1) {
const store = db.createObjectStore('notes', {
keyPath: 'id',
autoIncrement: true,
});
store.createIndex('by_tag', 'tag', { unique: false });
store.createIndex('by_date', 'createdAt', { unique: false });
}
if (oldVersion < 2) {
const tx = event.target.transaction;
const store = tx.objectStore('notes');
store.createIndex('by_priority', 'priority', { unique: false });
}
}; Pruefen Sie event.oldVersion, um nur die Migrationen auszufuehren, die der Benutzer noch nicht gesehen hat. Ein Benutzer, der von 0 (keine Datenbank) aufsteigt, fuehrt beide Bloecke aus. Einer, der von 1 aufsteigt, fuehrt nur den zweiten aus.
Alles zusammenfuegen
Hier ist ein minimales Skript, das alle obigen Funktionen ausfuehrt:
(async () => {
const db = await openDB();
const id = await addNote(db, 'Buy groceries', 'personal');
console.log('Added note with id:', id);
const all = await getAllNotes(db);
console.log('All notes:', all);
const tagged = await getNotesByTag(db, 'personal');
console.log('Personal notes:', tagged);
await updateNote(db, id, 'Buy groceries and cook dinner');
console.log('Updated note', id);
await deleteNote(db, id);
console.log('Deleted note', id);
})(); Oeffnen Sie die Browser-Konsole, um die Ausgabe zu sehen. Jede Operation ist asynchron, jeder Schreibvorgang geht durch eine Transaktion, und die Daten ueberleben Seitenaktualisierungen ohne Server.
Haeufige Fehler
| Fehler | Was passiert | Behebung |
|---|---|---|
Store ausserhalb von onupgradeneeded erstellen | InvalidStateError | Alle Schema-Aenderungen gehoeren in onupgradeneeded |
await in einem Transaktions-Callback verwenden | Die Transaktion schliesst sich automatisch, bevor das await aufgeloest wird | Halten Sie alle Store-Operationen synchron innerhalb eines Transaktions-Ticks, oder oeffnen Sie nach dem await eine neue Transaktion |
| Vergessen, die Versionsnummer zu erhoehen | onupgradeneeded feuert nie | Erhoehen Sie die Versionsganzzahl jedes Mal, wenn Sie das Schema aendern |
| Einen String uebergeben, wo ein Schluessel eine Zahl erwartet | Datensatz nicht gefunden | Passen Sie den Typ an: Wenn autoIncrement Zahlen generiert, fragen Sie mit einer Zahl ab |
IndexedDB ist ausfuehrlich, aber jedes Teil dient einem Zweck: Die Version steuert Ihr Schema, Transaktionen schuetzen Ihre Daten, und Indizes halten die Lesevorgaenge schnell. Sobald diese sieben Schritte zur Routine werden, haben Sie alles, was Sie brauchen, um strukturierte Daten auf dem Client zu speichern, sei es fuer Offline-Unterstuetzung, Caching oder einen Zustand, der die Sitzung ueberdauert.