Langsung ke konten utama

Key-Value Storage di Capacitor: Preferences vs SQLite

Bandingkan key-value storage di Capacitor: Preferences, SQLite key-value store, dan Keychain storage. Pelajari mana yang cocok untuk pengaturan, cache, dan secret.

Logo Capgo untuk perbandingan opsi penyimpanan key-value di Capacitor

Untuk penyimpanan key-value di Capacitor, gunakan @capacitor/preferences untuk beberapa pengaturan kecil, penyimpanan key-value berbasis SQLite untuk data yang lebih besar atau banyak, dan Keychain atau Keystore untuk rahasia. Panduan ini membandingkan opsi yang tersedia di Capacitor 8, menunjukkan code yang berfungsi untuk masing-masing, dan menjelaskan cara berpindah dari satu ke yang lain tanpa kehilangan data pengguna.

Kebanyakan aplikasi akhirnya memakai dua di antaranya sekaligus: Preferences atau SQLite untuk state aplikasi, dan secure storage untuk auth token.

The options at a glance

localStorage / IndexedDB @capacitor/preferences SQL Cepat KeyValueStore @capgo/capacitor-data-storage-sqlite @capgo/capacitor-native-biometric setData
Didukung oleh Penyimpanan WebView UserDefaults / SharedPreferences File SQLite File SQLite Keychain / Keystore
Tetap ada meski storage dibersihkan OS Tidak dijamin Ya Ya Ya Ya
Tipe nilai String (localStorage) String Nilai JSON, angka, boolean, Uint8Array String String
Rentang ukuran ideal Kecil Kecil, beberapa KB per nilai Nilai besar, banyak key Banyak key Sangat kecil (di bawah ~8 KB di Android)
Enkripsi Tidak Tidak Opsional SQLCipher, key dari secure storage Opsional SQLCipher, passphrase di config Ya, berbasis hardware
Namespace Per origin group opsi store opsi Tabel Nama key
Query berdasarkan prefix Tidak Tidak Daftar key filtervalues Tidak
Web Ya localStorage SQLite Wasm (OPFS) IndexedDB Tidak untuk rahasia

Mengapa localStorage

localStorage dan IndexedDB berjalan di dalam Capacitor WebView, tetapi datanya milik WebView, bukan penyimpanan native aplikasi Anda. OS dapat merebut penyimpanan WebView saat ruang perangkat menipis, dan dokumentasi Capacitor sendiri tidak merekomendasikannya untuk data yang tidak boleh hilang. Mengubah hostname atau skema aplikasi di capacitor.config juga mengubah origin, yang mengarahkan WebView ke penyimpanan lain yang kosong.

Gunakan sebagai cache yang bisa hilang. Gunakan salah satu opsi berbasis native untuk sisanya.

Opsi 1: Capacitor Preferences untuk pengaturan kecil

@capacitor/preferences adalah plugin resmi untuk data key-value ringan. Di iOS, data ditulis ke UserDefaults, di Android ke SharedPreferences, di web ke localStorage.

bun add @capacitor/preferences
bunx cap sync
import { Preferences } from '@capacitor/preferences';

interface Settings {
  theme: 'light' | 'dark' | 'system';
  fontScale: number;
}

const DEFAULTS: Settings = { theme: 'system', fontScale: 1 };

export async function loadSettings(): Promise<Settings> {
  const { value } = await Preferences.get({ key: 'settings' });
  return value ? { ...DEFAULTS, ...JSON.parse(value) } : DEFAULTS;
}

export async function saveSettings(settings: Settings): Promise<void> {
  await Preferences.set({ key: 'settings', value: JSON.stringify(settings) });
}

Gunakan untuk: tema, bahasa, flag onboarding, tab terakhir yang dipilih, dan feature toggle.

Jangan gunakan untuk:

  • Secrets. Nilai disimpan dalam plaintext di dalam app sandbox dan termasuk dalam backup perangkat.
  • Nilai besar. UserDefaults dan SharedPreferences memuat seluruh file ke memori. Blob JSON besar memperlambat setiap operasi baca dan tulis.
  • Banyak key yang perlu di-list atau dihapus berdasarkan prefix. keys() mengembalikan semuanya, jadi Anda memfilternya di JavaScript.

Preferences.configure({ group: 'MyApp' }) mengubah namespace tempat kunci disimpan. Grup default adalah CapacitorStorage.

Opsi 2: penyimpanan key-value SQLite

Setelah Anda menyimpan respons API yang di-cache, draf, antrean offline, atau ratusan entri, file SQLite adalah tempat yang lebih baik. File ini menangani nilai besar, penulisan bersifat atomik, dan dapat dienkripsi.

SQL KeyValueStore yang Cepat

@capgo/capacitor-fast-sql termasuk KeyValueStore kelas di atas koneksi SQLite-nya. Kelas ini membuat __kv_store tabel, menyimpan tipe nilai, dan mendukung beberapa penyimpanan bernama dalam satu database.

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

Fast SQL memerlukan dua pengaturan native (local networking iOS di Info.plist, aturan cleartext localhost di Android). Hal ini dibahas di Cara menggunakan SQLite di aplikasi Capacitor.

import { KeyValueStore } from '@capgo/capacitor-fast-sql';

const cache = await KeyValueStore.open({ database: 'app', store: 'api-cache' });

// Objects, arrays, numbers, booleans, null and Uint8Array are stored as-is
await cache.set('user:42', { id: 42, name: 'Ada', roles: ['admin'] });
await cache.set('lastSync', Date.now());

const user = (await cache.get('user:42')) as { id: number; name: string } | null;
const exists = await cache.has('lastSync');
const allKeys = await cache.keys();

await cache.remove('user:42');
await cache.clear(); // only clears the 'api-cache' store
await cache.close();

Ringkasan API: open(options), fromConnection(connection, store), set, get (mengembalikan null ketika tidak ada), has, remove, clear, keys, close.

Dua pola yang perlu diketahui:

Beberapa store, satu database. Panduan store option namespaces keys, sehingga settings, drafts dan api-cache bisa berjalan berdampingan dan dihapus secara terpisah.

Key-value di samping tabel asli. Jika aplikasi Anda sudah menggunakan Fast SQL untuk data relasional, gunakan kembali koneksi yang ada daripada membuka koneksi kedua:

import { FastSQL, KeyValueStore } from '@capgo/capacitor-fast-sql';

const db = await FastSQL.connect({ database: 'app' });
const drafts = await KeyValueStore.fromConnection(db, 'drafts');
// You own `db`: drafts.close() does not disconnect it

Nantinya, saat blob key-value berkembang menjadi sesuatu yang ingin Anda query (misalnya semua draf yang lebih lama dari seminggu), Anda bisa memindahkannya ke tabel yang semestinya di database yang sama.

Penyimpanan key-value terenkripsi

KeyValueStore.open menerima opsi yang sama dengan FastSQL.connect, termasuk encrypted dan encryptionKey di iOS dan Android:

import { KeyValueStore } from '@capgo/capacitor-fast-sql';
import { NativeBiometric } from '@capgo/capacitor-native-biometric';

async function getKey(): Promise<string> {
  const { isSaved } = await NativeBiometric.isDataSaved({ key: 'kv.key' });
  if (isSaved) return (await NativeBiometric.getData({ key: 'kv.key' })).value;
  const bytes = crypto.getRandomValues(new Uint8Array(32));
  const key = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
  await NativeBiometric.setData({ key: 'kv.key', value: key });
  return key;
}

const secureStore = await KeyValueStore.open({
  database: 'secure-kv',
  encrypted: true,
  encryptionKey: await getKey(),
});

Ini memerlukan SQLCipher di build native. Cara setup-nya, plus apa yang harus dilakukan kalau key hilang setelah restore backup, ada di Cara mengenkripsi database SQLite di Capacitor.

@capgo/capacitor-data-storage-sqlite

@capgo/capacitor-data-storage-sqlite adalah plugin key-value khusus di SQLite (awalnya oleh Jean Pierre Quéau, sekarang dikelola oleh Capgo). Value-nya berupa string, dan plugin ini menambahkan beberapa fitur tambahan: beberapa tabel per store, filter prefix dan suffix, serta import dan export JSON.

bun add @capgo/capacitor-data-storage-sqlite
bunx cap sync
import { CapgoCapacitorDataStorageSqlite as Store } from '@capgo/capacitor-data-storage-sqlite';

await Store.openStore({ database: 'appStore', table: 'settings' });

await Store.set({ key: 'locale', value: 'fr' });
await Store.set({ key: 'profile', value: JSON.stringify({ name: 'Ada' }) });

const { value } = await Store.get({ key: 'locale' });
const { result: hasProfile } = await Store.iskey({ key: 'profile' });
const { keys } = await Store.keys();

// Switch table inside the same store
await Store.setTable({ table: 'drafts' });
const { values } = await Store.filtervalues({ filter: 'note%' }); // values whose key starts with "note"

await Store.remove({ key: 'note-1' });
await Store.clear(); // clears the current table

filtervalues menerapkan filter sebagai pola SQL LIKE pola pada key (note% untuk prefix, %note untuk suffix, plain text untuk contains). Pola tersebut dimasukkan ke dalam string SQL, jadi hanya berikan filter yang Anda kontrol, jangan pernah masukkan input pengguna mentah.

Di web, plugin ini menggunakan IndexedDB melalui localforage, yang perlu Anda instal secara terpisah:

bun add localforage

Tentang enkripsinya: openStore({ encrypted: true, mode: 'secret' }) menggunakan SQLCipher dengan passphrase dari capacitor.config di bawah plugins.CapgoCapacitorDataStorageSqlite.encryptionSecret. Passphrase tersebut ikut dikirim di dalam app bundle Anda, dan jika Anda tidak mengaturnya, plugin akan memakai default bawaan. Anggap ini sebagai perlindungan terhadap pemeriksaan file sekilas, bukan terhadap orang yang membongkar app Anda. Untuk secret yang benar-benar rahasia, gunakan key dari Keychain atau Keystore, seperti pada contoh Fast SQL di atas.

SQL KeyValueStore yang Cepat atau data-storage-sqlite?

  • Pilih SQL Cepat KeyValueStore jika Anda ingin nilai bertipe tanpa JSON manual, nilai biner, kunci enkripsi runtime, atau sudah menggunakan Fast SQL untuk tabel.
  • Pilih data-storage-sqlite jika Anda membutuhkan fitur tabel dan filter, ekspor serta impor JSON, atau dukungan Electron.

Opsi 3: Keychain dan Keystore untuk data rahasia

Auth token, refresh token, API keys, dan kunci enkripsi database sebaiknya disimpan di secure storage platform. @capgo/capacitor-native-biometric (8.6.0 dan seterusnya) menyediakan API key-value generik di atas iOS Keychain dan Android Keystore:

bun add @capgo/capacitor-native-biometric
bunx cap sync
import { AccessControl, NativeBiometric } from '@capgo/capacitor-native-biometric';

// Silent read and write
await NativeBiometric.setData({ key: 'session.refreshToken', value: token });
const { value: refreshToken } = await NativeBiometric.getData({ key: 'session.refreshToken' });

// Require Face ID / Touch ID / fingerprint to read
await NativeBiometric.setData({
  key: 'wallet.pin',
  value: pin,
  accessControl: AccessControl.BIOMETRY_ANY,
});
const { value: secretPin } = await NativeBiometric.getSecureData({
  key: 'wallet.pin',
  reason: 'Confirm it is you',
});

await NativeBiometric.deleteData({ key: 'session.refreshToken' });

Jaga agar nilai tetap kecil. Dokumentasi plugin menyebutkan bahwa enkripsi berbasis Android Keystore bekerja optimal di bawah sekitar 8 KB per nilai. Jika Anda perlu melindungi banyak data, simpan satu kunci acak di sini dan gunakan untuk mengenkripsi database SQLite. Artikel kami tentang secure storage untuk token offline membahas penanganan token secara lebih mendalam.

Ingatlah bahwa item Keychain di iOS dapat bertahan setelah aplikasi di-uninstall. Pada peluncuran pertama setelah instalasi baru, bersihkan rahasia yang sudah kedaluwarsa jika aplikasi Anda mengharapkan kondisi bersih (simpan flag “first run done” di Preferences, yang akan dihapus saat uninstall, dan bersihkan entri Keychain jika flag tersebut tidak ada).

Memilih: daftar keputusan

  1. Apakah itu rahasia? Token, password, PIN, kunci. Gunakan penyimpanan Keychain atau Keystore.
  2. Apakah itu beberapa pengaturan kecil? Gunakan Preferences.
  3. Apakah ada banyak entri, JSON besar, data biner, atau cache yang ingin Anda kadaluarsakan dan bersihkan secara berkelompok? Gunakan key-value store SQLite.
  4. Apakah Anda perlu memfilter, mengurutkan, atau join data? Ini bukan lagi data key-value. Buat tabel, lihat panduan SQLite, atau gunakan lapisan bertipe seperti Kysely.

Migrasi data yang sudah ada antar store

Migrasi dari localStorage atau Preferences ke store SQLite adalah salinan sekali jalan saat startup. Lakukan sebelum bagian lain aplikasi membaca data, dan catat bahwa proses ini telah berjalan.

import { Preferences } from '@capacitor/preferences';
import { KeyValueStore, type KeyValueValue } from '@capgo/capacitor-fast-sql';

export async function migrateToSqliteStore(): Promise<KeyValueStore> {
  const kv = await KeyValueStore.open({ database: 'app', store: 'settings' });
  if (await kv.has('__migrated_v1')) return kv;

  const { keys } = await Preferences.keys();
  for (const key of keys) {
    const { value } = await Preferences.get({ key });
    if (value === null) continue;
    let parsed: KeyValueValue = value;
    try {
      parsed = JSON.parse(value) as KeyValueValue;
    } catch {
      // keep plain strings as strings
    }
    await kv.set(key, parsed);
  }

  // Also copy from localStorage if older versions used it
  for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i);
    if (key && !(await kv.has(key))) {
      await kv.set(key, localStorage.getItem(key));
    }
  }

  await kv.set('__migrated_v1', true);
  // Remove the old copies only after a release or two, once you are confident
  return kv;
}

Hapus rahasia dari store lama segera setelah disalin ke penyimpanan aman. Meninggalkan token di Preferences setelah dipindahkan ke Keychain tetap mempertahankan paparan awal.

Karena code ini berjalan di JavaScript, Anda dapat mengirimkannya dengan Capgo live update. Plugin SQLite itu sendiri bersifat native, jadi rilis yang menambahkannya memerlukan build store. Kirim plugin terlebih dahulu, lalu migrasi code.

Pemecahan Masalah

Nilai Preferences adalah null setelah pembaruan di web. Origin berubah (port, hostname, atau scheme), jadi localStorage adalah store yang berbeda dan kosong.

SQL Cepat KeyValueStore.open melempar error di iOS. Kanal HTTP lokal diblokir. Tambahkan NSAllowsLocalNetworking ke Info.plist dan build ulang aplikasi native.

get mengembalikan string padahal Anda mengharapkan object di data-storage-sqlite. Plugin tersebut hanya menyimpan string. JSON.parse nilai tersebut.

Secrets hilang setelah memulihkan backup di ponsel baru. Kunci Keystore dan item Keychain yang hanya untuk perangkat tidak berpindah ke perangkat keras baru. Perlakukan penyimpanan aman sebagai cache kredensial yang dapat Anda peroleh kembali dengan masuk ulang.

Pengaturan hilang setelah instal ulang, tetapi token masih ada di iOS. Diharapkan: Preferences dihapus bersama aplikasi, item Keychain dapat tetap ada. Gunakan pola flag first-run di atas.

Ringkasan

  • Preferences: pengaturan kecil, tidak sensitif.
  • SQLite key-value store (Fast SQL KeyValueStore atau @capgo/capacitor-data-storage-sqlite): banyak atau nilai besar, cache, draf, opsional terenkripsi.
  • Keychain atau Keystore (@capgo/capacitor-native-biometric setData): rahasia dan kunci enkripsi.

Referensi plugin: Dokumentasi Fast SQL, dokumen data-storage-sqlite, dan halaman plugin biometrik native.

Live updates untuk aplikasi Capacitor

Saat bug web-layer sudah live, kirim perbaikannya lewat Capgo alih-alih menunggu berhari-hari untuk persetujuan app store. Pengguna mendapat update di background, sementara perubahan native tetap mengikuti jalur review normal.

Terbaru dari Blog kami

Capgo memberikan insight terbaik yang Anda butuhkan untuk membuat aplikasi mobile yang benar-benar profesional.