Hauptinhalt überspringen

SQLite in einer Capacitor-App verwenden (2026-Leitfaden)

Lernen Sie, wie Sie SQLite in einer Capacitor-Anwendung verwenden: Wählen Sie einen Plugin, öffnen Sie eine Datenbank, führen Sie Migrationen, Transaktionen, Verschlüsselung und Webunterstützung durch, mit funktionierenden code.

Artikelcredits

Martin Donadieu

Autor

Valeria

Rezensent

Jordan

Redakteur

Verwendung von SQLite in einer Capacitor-Anwendung (2026-Leitfaden)

Um SQLite in einer Capacitor-Anwendung zu verwenden, installieren Sie ein natives SQLite-Plugin wie @capgo/capacitor-fast-sql, open a database once at startup, create your schema with versioned migrations, then read and write with parameterized SQL. This guide walks through that full setup on Capacitor 8 for iOS, Android and the web, including transactions, encryption, file locations, and the errors you are most likely to hit.

If you only need a few settings, SQLite is overkill. If you store lists, records, or anything you want to filter, sort, or sync, it is the right tool.

When SQLite ist die richtige Speicherwahl

Capacitor Apps haben mehrere Orte, an denen Daten gespeichert werden können. Sie lösen unterschiedliche Probleme:

Speicher Gut für Gut für
localStorage Grenzen / IndexedDB Im WebView lebt sie, der Betriebssystem kann sie verwerfen, keine echten Abfragen in localStorage
@capacitor/preferences Einige kleine Einstellungen (Thema, Sprache) Eine kleine Anzahl von Einstellungen (Thema, Sprache)
Schlüsseltresor / Speicher für Zertifikate Schlüsselkasten / Speicher für Verschlüsselungsschlüssel Kleine Geheimnisse nur
Dateien (@capacitor/filesystem) Bilder, PDFs, Exporte Keine Abfragen
SQLite Daten, Offline-Daten, Synchronisationswarte, Suche Datensätze, Offline-Daten, Synchronisationswarte, Suche

Pick SQLite when you need any of these: offline-first data, more than a few hundred records, filters and sorting, relations between tables, atomic multi-step writes, or encryption at rest for structured data.

Choosing a Capacitor SQLite plugin in 2026

Es gibt drei realistische Optionen für native SQLite auf Capacitor 8:

@capgo/capacitor-fast-sql @capacitor-community/sqlite @capgo/capacitor-data-storage-sqlite
Purpose Allgemeine SQL-Datenbank Allgemeine SQL-Datenbank String-Schlüsselwertspeicher auf SQLite
Plattformen iOS, Android, Web iOS, Android, Web, Electron iOS, Android, Web, Electron
Web-Engine Offizielles SQLite Wasm + OPFS jeep-sqlite (sql.js + IndexedDB) IndexedDB
Datenübertragung Lokale HTTP-Kanal, umgeht die Bridge Capacitor Bridge Capacitor Bridge
Verschlüsselung SQLCipher optional (iOS, Android) SQLCipher immer eingebunden SQLCipher, Passwort in Konfiguration
Zeilenformat Objekte mit Spalten als Schlüssel Objekte mit Spalten als Schlüssel Zeichenketten
ORM-Unterstützung Benutzerdefinierte Treiber (Kysely, Drizzle) TypeORM-integrierter Treiber Keine

Diese Anleitung verwendet Fast SQL. Es wird von Capgo unterstützt, verfolgt Capacitor Hauptversionen (Plugin v8 für Capacitor 8), ermöglicht SQLCipher optional und vermeidet die Serialisierung großer Ergebnisse über die Bridge. Wenn Sie bereits das Community-Plugin ausführen, lesen Sie unsere Vergleichs- und Migration-Anleitung erste.

Installieren Sie Fast SQL

bun add @capgo/capacitor-fast-sql
bunx cap sync

Fast SQL startet einen kleinen HTTP-Server, der an localhost und sendet SQL an ihn mit einem pro-Verbindungsbearer-Token. Beide Plattformen blockieren HTTP in Klartext durch Standard, daher erlauben Sie es nur für den Loopback.

iOS: Lokales Netzwerk erlauben

Add this to ios/App/App/Info.plist:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsLocalNetworking</key>
    <true/>
</dict>

NSAllowsLocalNetworking erstellt nur Loopback- und lokale Adressen ab. Es entspannt nicht die App-Transport-Sicherheit für Ihre echten API-Anrufe.

Android: Klartext an localhost erlauben

Erstellen android/app/src/main/res/xml/network_security_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="false">localhost</domain>
        <domain includeSubdomains="false">127.0.0.1</domain>
    </domain-config>
</network-security-config>

Referenzieren Sie es in android/app/src/main/AndroidManifest.xml:

<application
    android:networkSecurityConfig="@xml/network_security_config"
    ...>

Wenn Sie bereits eine Netzwerk-Sicherheitskonfiguration (z.B. für SSL-Zerlegung) haben, fügen Sie die domain-config Block anstatt einer zweiten Datei hinzu.

Öffnen Sie die Datenbank einmal

Erstellen Sie eine einzelne Modul, das die Verbindung besitzt. Das Öffnen derselben Datenbank mehrmals ist unverwertbar, und Sie möchten einen Ort haben, an dem Migrations durchgeführt werden.

// src/db/index.ts
import { FastSQL, type SQLConnection } from '@capgo/capacitor-fast-sql';
import { migrate } from './migrate';

let dbPromise: Promise<SQLConnection> | null = null;

export function getDb(): Promise<SQLConnection> {
  if (!dbPromise) {
    dbPromise = (async () => {
      const db = await FastSQL.connect({
        database: 'notes',
        walMode: true, // PRAGMA journal_mode = WAL
        performancePresets: true, // synchronous=NORMAL, busy_timeout, foreign_keys=ON
      });
      await migrate(db);
      return db;
    })().catch((error) => {
      dbPromise = null; // let the next call retry instead of reusing the failure
      throw error;
    });
  }
  return dbPromise;
}

walMode und performancePresets bereitgestellt werden, ab Version 8.0.49 des Plugins. performancePresets aktiviert auch foreign_keys, die SQLite standardmäßig auslässt, so dass ON DELETE CASCADE tatsächlich funktioniert.

Aufrufen Sie getDb() wo immer Sie die Datenbank benötigen. Der erste Aufruf öffnet und migriert, später aufrufende Anfragen nutzen die gleiche Promise. Wenn der Öffnen oder Migrieren fehlschlägt, versucht der nächste Aufruf es erneut.

Schema-Migrationen mit PRAGMA user_version

Fast SQL gibt Ihnen den direkten Zugriff auf SQL, kein Migrationstool, was in Ordnung ist, da SQLite bereits eine Versionsnummer im Dateikopf hat: PRAGMA user_versionHalte an einer geordneten Liste von Schritten und füge nur das Fehlende hinzu.

// src/db/migrate.ts
import type { SQLConnection } from '@capgo/capacitor-fast-sql';

// Index 0 = version 1, index 1 = version 2, ...
// Never edit a step after it shipped. Add a new one.
const steps: string[][] = [
  [
    `CREATE TABLE IF NOT EXISTS notes (
       id INTEGER PRIMARY KEY AUTOINCREMENT,
       title TEXT NOT NULL,
       body TEXT,
       created_at INTEGER NOT NULL
     )`,
  ],
  [
    `ALTER TABLE notes ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0`,
    `CREATE INDEX IF NOT EXISTS idx_notes_created_at ON notes (created_at)`,
  ],
];

export async function migrate(db: SQLConnection): Promise<void> {
  const rows = await db.query('PRAGMA user_version');
  const current = Number(rows[0]?.user_version ?? 0);

  for (let version = current; version < steps.length; version++) {
    await db.transaction(async (tx) => {
      for (const statement of steps[version]) {
        await tx.execute(statement);
      }
      // PRAGMA does not accept bound parameters, the value is a trusted integer
      await tx.execute(`PRAGMA user_version = ${version + 1}`);
    });
  }
}

Regeln, die dies sicherstellen:

  • Eine SQL-Anweisung pro execute() Aufruf. Beide native Implementierungen bereiten nur die erste Anweisung einer Zeichenkette vor, so dass "CREATE ...; CREATE ..." stillstehend eine davon ausgeführt wird.
  • Jede Version läuft in einer Transaktion, sodass ein Crash in der Mitte die Datenbank auf der vorherigen Version belässt und der Schritt beim nächsten Start erneut läuft.
  • Präferieren Sie additive Änderungen (neue Tabellen, neue nullable Spalten). SQLite unterstützt ALTER TABLE ... DROP COLUMN und RENAME COLUMN in recent versions, but the system SQLite on older Android releases may be behind, so the portable way to reshape a table is create new table, copy, drop old, rename.

Migrations und Live-Updates

Wenn Sie JavaScript-Änderungen mit Capgo Live-Updates, wird Ihre Migration code mit dem Bundle geliefert. Das ist praktisch, da eine Schemaänderung keinen Speicher-Release erfordert. Es bedeutet auch, dass ein Rollback einen älteren Bundle auf einem neuen Schema legen kann. Halten Sie Migrations rückwärts kompatibel (Fügen Sie Spalten hinzu, fügen Sie sie nicht in der gleichen Release um, die sie nicht mehr verwendet) und ein älteres Bundle funktioniert weiterhin.

Lesen und Schreiben von Daten

SQLConnection geben Ihnen vier Methoden, die Sie alle Zeit verwenden werden:

Methode Rückgabe Verwendung für
query(sql, params) SQLRow[] SELECT- und PRAGMA-Leserequests
run(sql, params) { rowsAffected, insertId } INSERT-, UPDATE- und DELETE-Anfragen
execute(sql, params) { rows, rowsAffected, insertId } Jederzeit, vollständiger Ergebnis
executeBatch(ops) SQLResult[] Viele Anweisungen in einem Rundgang

Binden Sie immer Werte mit ? Platzhalter. Bauen Sie keine SQL-Anweisungen mit String-Konkatenation aus Benutzereingaben.

// src/db/notes.ts
import { getDb } from './index';

export interface Note {
  id: number;
  title: string;
  body: string | null;
  created_at: number;
  pinned: number;
}

export async function addNote(title: string, body: string | null): Promise<number> {
  const db = await getDb();
  const { insertId } = await db.run(
    'INSERT INTO notes (title, body, created_at) VALUES (?, ?, ?)',
    [title, body, Date.now()],
  );
  if (insertId == null) throw new Error('Insert did not return an id');
  return insertId;
}

export async function listNotes(search = ''): Promise<Note[]> {
  const db = await getDb();
  const rows = await db.query(
    'SELECT id, title, body, created_at, pinned FROM notes WHERE title LIKE ? ORDER BY pinned DESC, created_at DESC',
    [`%${search}%`],
  );
  return rows as unknown as Note[];
}

export async function setPinned(id: number, pinned: boolean): Promise<void> {
  const db = await getDb();
  await db.run('UPDATE notes SET pinned = ? WHERE id = ?', [pinned ? 1 : 0, id]);
}

export async function deleteNote(id: number): Promise<number> {
  const db = await getDb();
  const { rowsAffected } = await db.run('DELETE FROM notes WHERE id = ?', [id]);
  return rowsAffected;
}

Zeilen kommen als einfache Objekte zurück, die durch Spaltennamen gekennzeichnet sind, daher gibt es keine Spalten/Werte-Abstimmung. SQLite kennt keine boolesche Typen, also speichern Sie 1 und 0 und wandeln Sie sie am Rand Ihres Datenlayers um, wie setPinned tun.

Transaktionen und Batch-Schreibvorgänge

Umfasst jede mehrschrittige Schreiboperation eine Transaktion. Sie hält die Daten konsistent und ist viel schneller, da SQLite nur einmal pro Commit auf das Disk-System schreibt, anstatt einmal pro Anweisung.

import { getDb } from './index';

export async function importNotes(items: { title: string; body: string }[]) {
  const db = await getDb();
  const now = Date.now();

  await db.transaction(async (tx) => {
    await tx.executeBatch(
      items.map((item) => ({
        statement: 'INSERT INTO notes (title, body, created_at) VALUES (?, ?, ?)',
        params: [item.title, item.body, now],
      })),
    );
  });
}

transaction() commits when the callback resolves and rolls back if it throws. If you need manual control there is also beginTransaction(), commit() und rollback() auf der Verbindung.

All Anfragen nutzen eine einzelne native Verbindung pro Datenbank. Während eine Transaktion offen ist, werden andere code-Anfragen, die dieselbe Datenbank schreiben, innerhalb dieser Transaktion ausgeführt. Halten Sie Transaktionen kurz und vermeiden Sie es, innerhalb von ihnen Netzwerk-Anfragen abzuwarten.

Speichern Sie binäre Daten

Passen Sie ein Uint8Array und Sie erhalten ein Uint8Array zurück. Der Plugin kodiert es bas64 um für den Transport und speichert einen echten BLOB.

const db = await getDb();
await db.execute('CREATE TABLE IF NOT EXISTS thumbnails (note_id INTEGER PRIMARY KEY, data BLOB)');
await db.run('INSERT OR REPLACE INTO thumbnails (note_id, data) VALUES (?, ?)', [noteId, bytes]);

const [row] = await db.query('SELECT data FROM thumbnails WHERE note_id = ?', [noteId]);
const image = row?.data as Uint8Array | undefined;

Halten Sie große Medien (Fotos, Videos) als Dateien und speichern Sie nur den Pfad in SQLite. BLOBs sind gut für Miniaturansichten, kleine Anhänge und Embeddings geeignet.

Verschlüsseln Sie die Datenbank

Fast SQL unterstützt SQLCipher auf iOS und Android. Es ist optional, daher schicken Apps, die es nicht benötigen, es nicht mit:

const db = await FastSQL.connect({
  database: 'vault',
  encrypted: true,
  encryptionKey: keyFromSecureStorage,
});

Bei Android fügen Sie net.zetetic:sqlcipher-android zu Ihrer App’s build.gradleBei iOS wechseln Sie die Pod zu der CapgoCapacitorFastSql/SQLCipher subspezifikation. Die Erstellung der Schlüssel, das Speichern in der Keychain oder Keystore, die Verschlüsselung einer bestehenden plaintext-Datenbank und das Rotieren von Schlüsseln werden in einzelnen Schritten abgedeckt. How Capacitor eine SQLite-Datenbank zu verschlüsseln. Die Verschlüsselung ist auf der Web nicht verfügbar

Web-Unterstützung

Im Web verwendet Fast SQL die offizielle @sqlite.org/sqlite-wasm und speichert das Datei im Origin Private File System (OPFS). Es ist eine echte SQLite-Datei, nicht eine im Speicher gehaltene Kopie, die in IndexedDB geschrieben wird.

OPFS benötigt Cross-Origin-Isolation, daher müssen Ihr Entwicklungs-Server und Ihr Produktions-Host zwei Header senden:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Mit Vite:

// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    headers: {
      'Cross-Origin-Opener-Policy': 'same-origin',
      'Cross-Origin-Embedder-Policy': 'require-corp',
    },
  },
  optimizeDeps: {
    exclude: ['@sqlite.org/sqlite-wasm'],
  },
});

Wenn die Header fehlen, meldet der Plugin eine Warnung und fällt auf eine nicht-persistente Datenbank zurück: Alles funktioniert, bis Sie neu laden. CapgoCapacitorFastSql.configureWeb({ useOpfs, worker }) erlaubt Ihnen, die Standards vor der ersten connect(), und es ist ein No-Op auf nativem.

require-corp Wenn Sie es aktivieren, blockieren auch Quellen, die keine CORP- oder CORS-Header senden, also testen Sie dritte Einbettungen, wenn Sie es aktivieren.

Wo die Datenbank-Datei lebt

Plattform Standort Hinweise
iOS Documents/<name>.db Eingeschränkt auf iCloud und Geräte-Backup durch Standard
Android files/<name>.db im internen Speicher des Apps Unterworfen den Android Auto Backup-Regeln
Website OPFS für die Ursprungsseite Gelöscht, wenn der Benutzer die Site-Daten löscht

Zwei praktische Konsequenzen. Zuerst wird das File beim Entfernen der App auf beiden mobilen Plattformen gelöscht. Zweitens können Backups das Datenbank-File auf einen neuen Gerät wiederherstellen. Das ist normalerweise das, was Sie wollen, aber wenn die Datenbank mit einem Schlüssel verschlüsselt ist, der nicht mit dem Backup reist, kann das wiederhergestellte File nicht geöffnet werden. Die Verschlüsselungshinweise decken ab, wie Sie damit umgehen.

Leistungscheckliste

  • Ein walMode. Leser blockieren den Schreiber nicht.
  • Batcheschreiben in einer Transaktion mit executeBatch. Dies ist der größte Gewinn für Importe und Synchronisierung.
  • Indexe für Spalten hinzufügen, die Sie filtern oder sortieren, und überprüfen Sie dann mit EXPLAIN QUERY PLAN.
  • Wählen Sie nur die Spalten aus, die Sie benötigen. Große TEXT und BLOB Spalten kosten auch Übertragungszeit, selbst über den HTTP-Kanal.
  • Paginieren mit WHERE created_at < ? ORDER BY created_at DESC LIMIT 50 anstatt großen OFFSET Werten.
  • Nachdem Sie viel Daten gelöscht haben, führen Sie VACUUM gelegentlich aus, um die Datei zu verkleinern. Es überarbeitet die gesamte Datenbank, also tun Sie es während der Leerlaufzeit.

Troubleshooting

SQL execution failed nachdem Sie sich bei iOS verbunden haben. auf iOS direkt nach dem Verbinden. NSAllowsLocalNetworking ist richtig Info.plist und die native App neu erstellen, eine Live-Neuladung reicht nicht aus.

CLEARTEXT communication to localhost not permitted auf Android. Die Netzwerksicherheitskonfiguration fehlt oder wird nicht aus der Manifestdatei referenziert.

Nur die erste Anweisung wurde ausgeführt. You passed several statements in one string. Split them, or use executeBatch.

no such table nach einer Aktualisierung. Die Migration wurde nicht ausgeführt oder wurde gegen eine andere Datenbank durchgeführt. Protokollieren PRAGMA user_version bei dem Start und vergleichen Sie es mit Ihrem Schritte-Array.

Ein Filter auf eine boolesche oder berechnete Werte trifft nie auf Android. Bei Leserequests bindet die Android-Implementierung Parameter als Text. Vergleiche gegen eine Spalte mit INTEGER-Affinität funktionieren immer noch, da SQLite den Text umwandelt, aber Vergleiche gegen Ausdrücke (z.B. json_extract(...) = ? oder count(*) > ?) vergleiche Text mit einer Zahl. Pass 1/0 oder true/false, und verwenden CAST(? AS INTEGER) wenn Sie es mit einer Ausdruck vergleichen.

INSERT ... RETURNING gibt keine Zeilen auf Android zurück. In der aktuellen Pluginversion gibt Android nur Zeilen zurück, die mit SELECT, PRAGMA oder EXPLAIN. Use insertId von run() und wählen Sie die Zeile danach aus. Das Gleiche gilt für Abfragen, die mit WITHbeginnen: Um sie so zu umschreiben, dass sie mit SELECTbeginnen, oder testen Sie sie bereits auf Android.

Daten verschwinden beim Neuladen im Browser. OPFS ist nicht verfügbar, fast immer, weil die COOP/COEP-Kopfzeilen auf dem bereitgestellten Host fehlen.

Encryption is not available in this build auf iOS. Sie haben encrypted: true ohne auf die SQLCipher-Unterpakete umzuschalten.

Geht es weiter

Roh-SQL ist für kleine Apps in Ordnung. Sobald Sie mehr als ein paar Tabellen haben, hilft eine abgetastete Ebene:

Die Plugin-Referenz befindet sich im Fast SQL-Dokumentation und der Plugin-SeiteFast SQL benötigt eine native Rekonstruktion, wenn Sie es hinzufügen oder seine Konfiguration ändern. Wenn Sie nicht wollen, dass Sie einen Mac für das halten müssen, Capgo Build Kann signierte iOS- und Android-Binärdateien im Cloud produzieren. Da seit April 2026 App Store-Uploads mit Xcode 26 oder später erstellt werden müssen, überprüfen Sie bitte Ihr Build-System vor der nächsten Veröffentlichung.

Live-Updates für Capacitor-Anwendungen

Wenn ein Web-Schicht-Bug aktiv ist, schicken Sie die Reparatur über Capgo anstatt Tage für die Genehmigung im App-Store abzuwarten. Die Benutzer erhalten die Aktualisierung im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neueste von unserem Blog

Capgo bietet Ihnen die besten Einblicke, die Sie benötigen, um eine wirklich professionelle Mobilanwendung zu erstellen.