Langsung ke isi konten

Bagaimana Menggunakan SQLite di Aplikasi Capacitor (Guida 2026)

Apa yang Anda butuhkan untuk menggunakan SQLite di aplikasi Capacitor: pilih plugin, buka database, jalankan migrasi, transaksi, enkripsi, dan dukungan web, dengan code yang berfungsi.

Kredit Artikel

Martin Donadieu

Pengarang

Valeria

Pengulas

Jordan

Pengarang

Berikut Cara Menggunakan SQLite di Aplikasi Capacitor (Guida 2026)

Untuk menggunakan SQLite di aplikasi Capacitor, instal plugin SQLite native seperti @capgo/capacitor-fast-sql, buka database sekali pada startup, buat schema dengan migrasi versi, kemudian baca dan tulis dengan SQL parameterized. Panduan ini menjelaskan setup lengkap tersebut pada Capacitor 8 untuk iOS, Android, dan web, termasuk transaksi, enkripsi, lokasi file, dan kesalahan yang paling mungkin Anda temui.

Jika Anda hanya membutuhkan beberapa pengaturan, SQLite adalah berlebihan. Jika Anda menyimpan daftar, rekaman, atau apa pun yang ingin Anda filter, urutkan, atau sinkronkan, itu adalah alat yang tepat.

Ketika SQLite adalah pilihan penyimpanan yang tepat

Aplikasi Capacitor memiliki beberapa tempat untuk menyimpan data. Mereka menyelesaikan masalah yang berbeda:

Penyimpanan Baik untuk Batasan
localStorage / IndexedDB Cache sementara Berada di dalam WebView, sistem operasi dapat mengusirnya, tidak ada kueri nyata di localStorage
@capacitor/preferences Beberapa pengaturan kecil (tema, bahasa) Nilai string saja, tidak untuk data besar, tidak terenkripsi
Penyimpanan Keychain / Keystore Token, kunci enkripsi Rahasia kecil saja
File (@capacitor/filesystem) Gambar, PDF, ekspor Tidak ada querying
SQLite Catatan, data offline, antrian sinkronisasi, pencarian Manajemen skema dan migrasi

Pilih SQLite ketika Anda membutuhkan salah satu dari berikut: data offline pertama, lebih dari beberapa ratus catatan, filter dan pengurutan, hubungan antar tabel, penulisan multi-langkah atomik, atau enkripsi pada data struktur untuk data yang ada di rest.

Choosing a Capacitor SQLite plugin in 2026

Ada tiga opsi yang realistis untuk SQLite native pada Capacitor 8:

@capgo/capacitor-fast-sql @capacitor-community/sqlite @capgo/capacitor-data-storage-sqlite
Tujuan Database SQL umum Database SQL umum Penyimpanan nilai kunci-string pada SQLite
Platform iOS, Android, Web iOS, Android, Web, Electron iOS, Android, Web, Electron
Mesin Web SQLite Wasm + OPFS Resmi jeep-sqlite (sql.js + IndexedDB) IndexedDB
Transportasi Data Saluran HTTP Lokal, menghindari jembatan Capacitor jembatan Capacitor jembatan
Enkripsi SQLCipher Opsional (iOS, Android) SQLCipher selalu dibundel SQLCipher, sandi dalam konfigurasi
Format Baris Benda-benda yang dikunci oleh kolom Benda-benda yang dikunci oleh kolom String
Support ORM Pengemudi kustom (Kysely, Drizzle) Pengemudi TypeORM bawaan Tidak ada

Guid ini menggunakan Fast SQL. Dibuat oleh Capgo, mengikuti Capacitor versi utama (plugin v8 untuk Capacitor 8), menjaga SQLCipher sebagai pilihan, dan menghindari serialisasi hasil set besar melalui bridge. Jika Anda sudah menjalankan plugin komunitas, baca panduan perbandingan dan migrasi kami bandingan dan panduan migrasi kami pertama.

Instal Fast SQL

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

Fast SQL memulai sebuah server HTTP kecil yang terikat ke localhost dan mengirimkan SQL kepadanya dengan token bearer per-koneksi. Kedua platform ini melarang HTTP jelas oleh default, jadi izinkanlah hanya untuk loopback saja.

iOS: izinkan jaringan lokal

Tambahkan ini ke ios/App/App/Info.plist:

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

NSAllowsLocalNetworking hanya mencakup loopback dan alamat lokal. Ini tidak menghilangkan App Transport Security untuk panggilan API nyata Anda.

Android: izinkan teks jelas ke localhost

Buat 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>

Referensikan android/app/src/main/AndroidManifest.xml:

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

Jika Anda sudah memiliki konfigurasi keamanan jaringan (untuk SSL pinning, misalnya), tambahkan domain-config Blok untuk itu daripada membuat file kedua.

Buka database sekali

Buat modul tunggal yang mengelola koneksi. Membuka database yang sama secara berulang-ulang adalah tidak efisien, dan Anda ingin satu tempat untuk menjalankan migrasi.

// 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 dan performancePresets tersedia dari versi plugin 8.0.49. performancePresets juga mengaktifkan foreign_keys, yang SQLite tinggalkan secara default, sehingga ON DELETE CASCADE benar-benar berfungsi.

Panggil getDb() di mana saja Anda membutuhkan database. Panggilan pertama membuka dan melakukan migrasi, panggilan selanjutnya menggunakan janji yang sama. Jika membuka atau melakukan migrasi gagal, panggilan selanjutnya mencoba lagi.

Migrasi skema dengan PRAGMA user_version

SQL Cepat memberikan Anda SQL mentah, bukan kerangka migrasi, yang tidak masalah karena SQLite sudah memiliki slot versi di header file: PRAGMA user_versionSimpan array langkah yang terurut dan terapkan hanya apa yang hilang.

// 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}`);
    });
  }
}

Aturan yang menjaga ini aman:

  • Satu statement SQL per execute() Panggilan. Implementasi native hanya mempersiapkan statement SQL pertama dari sebuah string, sehingga "CREATE ...; CREATE ..." langsung menjalankan salah satu dari mereka.
  • Setiap versi berjalan dalam transaksi, sehingga kegagalan di tengah meninggalkan database pada versi sebelumnya dan langkah berulang kembali pada peluncuran berikutnya.
  • Ubah-ubah yang menambahkan (membuat tabel baru, menambahkan kolom nullable baru). SQLite mendukung ALTER TABLE ... DROP COLUMN dan RENAME COLUMN dalam versi terbaru, tetapi sistem SQLite pada rilis Android yang lebih tua mungkin ketinggalan, sehingga cara yang lebih portable untuk mengubah struktur tabel adalah membuat tabel baru, menyalin, menghapus tabel lama, dan menggantinya.

Migrasi dan pembaruan live

Jika Anda mengirimkan perubahan JavaScript dengan Capgo pembaruan live, migrasi code akan dikirimkan bersama dengan bundle. Hal ini sangat menguntungkan, karena perubahan schema tidak memerlukan rilis penyimpanan. Namun, hal ini juga berarti bahwa rollback dapat mengembalikan bundle yang lebih tua di atas schema yang lebih baru. Pastikan migrasi kompatibel ke belakang (tambahkan kolom, jangan rename atau dropnya dalam rilis yang sama yang menghentikan penggunaannya) dan bundle yang lebih tua masih dapat berjalan.

Membaca dan menulis data

SQLConnection memberikan empat metode yang akan Anda gunakan secara terus-menerus:

Metode Mengembalikan Gunakan untuk
query(sql, params) SQLRow[] Pemilihan dan baca PRAGMA
run(sql, params) { rowsAffected, insertId } INSERT, UPDATE, DELETE
execute(sql, params) { rows, rowsAffected, insertId } Apapun, hasil penuh
executeBatch(ops) SQLResult[] Banyak statement dalam satu perjalanan

Selalu ikat nilai dengan ? tempat pengganti. Tidak pernah bangun SQL dengan penggabungan string dari input pengguna.

// 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;
}

Sekarang datang sebagai objek sederhana yang dikunci oleh nama kolom, sehingga tidak ada langkah pemetaan kolom/nilai. SQLite tidak memiliki jenis boolean, sehingga simpan 1 dan 0 dan konversi di tepi lapisan data Anda, seperti setPinned seperti itu.

Transaksi dan penulisan batch

Selubungi setiap penulisan multi-langkah dalam transaksi. Ini menjaga konsistensi data dan sangat lebih cepat, karena SQLite sinkron ke disk sekali per komit daripada sekali per statement.

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() transaksi akan ditransaksikan ketika callback diselesaikan dan dibatalkan jika terjadi kesalahan. Jika Anda memerlukan kontrol manual, ada juga beginTransaction(), commit() dan rollback() pada koneksi.

Semua panggilan menggunakan koneksi native tunggal per database. Sementara transaksi terbuka, lainnya code yang menulis ke database yang sama berjalan di dalamnya. Jaga transaksi singkat dan hindari menunggu permintaan jaringan di dalamnya.

Menyimpan data biner

Pass sebuah Uint8Array dan Anda mendapatkan sebuah Uint8Array Kembali. Plugin mengenkripsi base64-nya untuk transportasi dan menyimpan BLOB yang sebenarnya.

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;

Tetapkan media besar (foto, video) sebagai file dan simpan hanya jalur di SQLite. BLOBs cocok untuk miniatur, lampiran kecil, dan penggabungan.

Enkripsi basis data

SQLCipher didukung di iOS dan Android. Ini adalah fitur yang dapat diaktifkan, sehingga aplikasi yang tidak membutuhkannya tidak akan mengirimkannya.

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

Pada Android Anda tambahkan net.zetetic:sqlcipher-android ke aplikasi Anda's build.gradle. Pada iOS Anda beralih ke pod ke CapgoCapacitorFastSql/SQLCipher subspec. Membuat kunci, menyimpannya di Keychain atau Keystore, mengenkripsi basis data plaintext yang ada dan memutar kunci dijelaskan secara langkah demi langkah di Bagaimana mengenkripsi basis data SQLite di Capacitor. Enkripsi tidak tersedia di web.

Bantuan Web

Pada web SQL Cepat menggunakan resmi @sqlite.org/sqlite-wasm membangun dan menyimpan file di Sistem File Pribadi Asal (OPFS). Ini adalah file SQLite yang sebenarnya, bukan salinan yang disimpan di memori dan dihapus ke IndexedDB.

OPFS memerlukan isolasi asal-usul yang berbeda, jadi server pengembang dan host produksi Anda harus mengirim dua header:

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

Dengan 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'],
  },
});

Jika header tersebut hilang, plugin mencetak peringatan dan kembali ke basis data yang tidak berlangsung: segalanya berfungsi sampai Anda reload. CapgoCapacitorFastSql.configureWeb({ useOpfs, worker }) memungkinkan Anda mengubah default sebelum pertama kali connect()dan itu adalah operasi tidak berpengaruh pada native.

require-corp juga menghalangi gambar dan skrip asal-usul yang berbeda yang tidak mengirimkan header CORP atau CORS, sehingga uji embed pihak ketiga ketika Anda mengaktifkannya.

Dimana file basis data berada

Platform Lokasi Catatan
iOS Documents/<name>.db Termasuk dalam cadangan iCloud dan perangkat secara default
Android files/<name>.db dalam penyimpanan internal aplikasi Tergantung pada aturan Android Auto Backup
Web OPFS untuk asal halaman Dihapus jika pengguna menghapus data situs

Dua konsekuensi praktis. Pertama, menghapus aplikasi menghapus file pada kedua platform mobile. Kedua, cadangan dapat memulihkan file basis data ke perangkat baru. Itu biasanya apa yang Anda inginkan, tetapi jika basis data dienkripsi dengan kunci yang tidak berjalan bersama cadangan, file yang dipulihkan tidak dapat dibuka. Panduan enkripsi menjelaskan cara mengatasi hal itu.

Daftar periksa kinerja

  • Mengaktifkan walModePembaca berhenti menghalangi penulis.
  • Mengirimkan tulisan dalam transaksi dengan executeBatch. Ini adalah kemenangan terbesar untuk import dan sinkronisasi.
  • Tambahkan indeks untuk kolom yang Anda filter atau sortir, kemudian cek dengan EXPLAIN QUERY PLAN.
  • Pilih hanya kolom yang Anda butuhkan. Besar TEXT dan BLOB kolom bahkan biaya waktu transfer bahkan melalui saluran HTTP.
  • Paginasi dengan WHERE created_at < ? ORDER BY created_at DESC LIMIT 50 alih daripada besar OFFSET nilai.
  • Setelah menghapus banyak data, jalankan VACUUM secara berkala untuk mengurangi ukuran file. Ini menulis ulang seluruh database, jadi lakukan saat waktu senggang.

Troubleshooting

SQL execution failed di iOS setelah terhubung. Permintaan HTTP lokal diblokir. Periksa bahwa NSAllowsLocalNetworking ada di tempat yang tepat Info.plist dan bangun kembali aplikasi asli, reload hidup tidak cukup.

CLEARTEXT communication to localhost not permitted di Android. Konfigurasi keamanan jaringan tidak ada atau tidak disebutkan dari manifest.

Hanya statement pertama yang dieksekusi. Anda melewatkan beberapa statement dalam satu string. Pisahkan mereka, atau gunakan executeBatch.

no such table setelah pembaruan. Pemigrasi tidak berjalan, atau berjalan melawan nama basis data lain. Log PRAGMA user_version di awal dan bandingkan dengan array langkah Anda.

Filter pada nilai boolean atau nilai yang dihitung tidak pernah cocok di Android. Untuk kueri baca implementasi Android memasang parameter sebagai teks. Perbandingan terhadap kolom dengan afinitas INTEGER masih berfungsi karena SQLite mengonversi teks, tetapi perbandingan terhadap ekspresi (misalnya json_extract(...) = ? atau count(*) > ?bandingkan teks dengan angka. Lanjutkan 1/0 sebaliknya dari true/false, dan gunakan CAST(? AS INTEGER) ketika membandingkan terhadap ekspresi.

INSERT ... RETURNING tidak mengembalikan baris pada Android. Dalam versi plugin saat ini, Android hanya mengembalikan baris untuk statement yang dimulai dengan SELECT, PRAGMA atau EXPLAINGunakan insertId dari run() dan pilih baris setelahnya. Hal yang sama berlaku untuk kueri yang dimulai dengan WITHtutup mereka dengan cara agar dimulai dengan SELECTatau tes mereka di Android lebih dulu.

Data menghilang ketika di-reload di browser. OPFS tidak tersedia, hampir selalu karena header COOP/COEP hilang di host yang di-deploy.

Encryption is not available in this build di iOS. Anda lulus encrypted: true tanpa harus beralih ke subspec SQLCipher.

Melanjutkan

SQL mentah cocok untuk aplikasi kecil. Ketika Anda memiliki lebih dari beberapa tabel, lapisan yang ditipekan membantu:

Referensi plugin hidup di Dokumen SQL cepat dan halaman plugin. Fast SQL needs a native rebuild when you add it or change its configuration. If you do not want to maintain a Mac for that, Capgo Build can produce signed iOS and Android binaries in the cloud. Since April 2026 App Store uploads must be built with Xcode 26 or later, so check your build machine before the next release.

Update Terus untuk Aplikasi Capacitor

Ketika bug layer web masih aktif, kirimkan perbaikan melalui Capgo daripada menunggu hari-hari untuk persetujuan toko aplikasi. Pengguna mendapatkan update di latar belakang sementara perubahan native tetap dalam jalur review normal.

Dukungan Manusia dari Martin

Mulai Sekarang

Terbaru dari Blog Kami

Capgo memberikan Anda wawasan terbaik yang Anda butuhkan untuk menciptakan aplikasi mobile profesional yang sebenarnya.