Aller au contenu principal

Stockage clé-valeur dans Capacitor : Preferences vs SQLite

Comparez le stockage clé-valeur dans Capacitor : Preferences, SQLite et Keychain. Découvrez la solution idéale pour vos réglages, votre cache et vos secrets.

Logo Capgo pour une comparaison des options de stockage clé-valeur dans Capacitor

Pour le stockage clé-valeur dans Capacitor, utilisez @capacitor/preferences pour quelques petites préférences, un store clé-valeur basé sur SQLite pour tout ce qui est plus volumineux ou plus nombreux, et le Keychain ou Keystore pour les secrets. Ce guide compare les options disponibles sur Capacitor 8, présente du code fonctionnel pour chacune, et explique comment passer de l’une à l’autre sans perdre les données utilisateur.

La plupart des apps utilisent deux de ces solutions en même temps : Preferences ou SQLite pour l’état de l’app, et un stockage sécurisé pour le token d’authentification.

Les options en un coup d'œil

localStorage / IndexedDB @capacitor/preferences SQL rapide KeyValueStore @capgo/capacitor-data-storage-sqlite @capgo/capacitor-native-biometric setData
Basé sur Stockage WebView UserDefaults / SharedPreferences Fichier SQLite Fichier SQLite Keychain / Keystore
Survit au nettoyage du stockage par l'OS Non garanti Oui Oui Oui Oui
Types de valeurs Chaînes de caractères (localStorage) Chaînes Valeurs JSON, nombres, booléens, Uint8Array Chaînes Chaînes
Plage de taille idéale Petite Petit, quelques Ko par valeur Valeurs volumineuses, nombreuses clés Nombreuses clés Très petit (moins de 8 Ko environ sur Android)
Chiffrement Non Non SQLCipher optionnel, clé issue du stockage sécurisé SQLCipher optionnel, phrase secrète dans la config Oui, via le matériel
Espaces de noms Par origine group option store option Tables Noms des clés
Interroger par préfixe Non Non Lister les clés filtervalues Non
Le Web Oui localStorage SQLite Wasm (OPFS) IndexedDB Pas pour les secrets

Pourquoi pas localStorage

localStorage et IndexedDB fonctionnent à l'intérieur d'une Capacitor WebView, mais les données appartiennent à la WebView et non au stockage natif de votre application. L'OS peut récupérer l'espace de stockage de la WebView quand l'appareil manque de place, et la documentation de Capacitor recommande de ne pas s'appuyer dessus pour des données critiques. Modifier l'hostname ou le scheme de l'application dans capacitor.config change également l'origine, ce qui redirige la WebView vers un stockage différent et vide.

Utilisez-les comme un cache pouvant disparaître. Utilisez l'une des options basées sur le natif pour tout le reste.

Option 1 : Capacitor Preferences pour les petits réglages

@capacitor/preferences est le plugin officiel pour les données clé-valeur légères. Sur iOS, il écrit dans UserDefaults, sur Android dans SharedPreferences, sur le web dans 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) });
}

Utilisez-le pour : le thème, la langue, les flags d'onboarding, le dernier onglet sélectionné, les feature toggles.

Ne l'utilisez pas pour :

  • Secrets. Les valeurs sont stockées en clair dans le sandbox de l'app et sont incluses dans les sauvegardes de l'appareil.
  • Valeurs volumineuses. UserDefaults et SharedPreferences chargent l'intégralité du fichier en mémoire. Les gros blobs JSON ralentissent chaque lecture et écriture.
  • De nombreuses clés à lister ou à effacer par préfixe. keys() renvoie tout, vous devez donc filtrer en JavaScript.

Preferences.configure({ group: 'MyApp' }) change le namespace sous lequel les clés sont stockées. Le groupe par défaut est CapacitorStorage.

Option 2 : un store clé-valeur SQLite

Dès que vous stockez des réponses API en cache, des brouillons, des files d'attente hors ligne ou des centaines d'entrées, un fichier SQLite est préférable. Il gère les valeurs volumineuses, les écritures sont atomiques et vous pouvez le chiffrer.

KeyValueStore SQL rapide

@capgo/capacitor-fast-sql inclut un KeyValueStore classe s'appuyant sur sa connexion SQLite. Elle crée une __kv_store table, conserve le type de valeur et prend en charge plusieurs stores nommés dans une même base de données.

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

Fast SQL nécessite deux réglages natifs (réseau local iOS dans Info.plist, une règle de trafic en clair pour localhost sur Android). Ils sont abordés dans Comment utiliser SQLite dans une application 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();

Résumé API : open(options), fromConnection(connection, store), set, get (renvoie null lorsqu’il est manquant), has, remove, clear, keys, close.

Deux patterns à connaître :

Plusieurs stores, une seule base de données. Le store l’option namespace préfixe les clés, donc settings, drafts et api-cache peuvent coexister et être effacées indépendamment.

Key-value à côté de vraies tables. Si votre application utilise déjà Fast SQL pour des données relationnelles, réutilisez la connexion au lieu d’en ouvrir une seconde :

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

Plus tard, si un blob key-value devient quelque chose que vous voulez interroger (tous les brouillons de plus d’une semaine, par exemple), vous pouvez le déplacer dans une table appropriée de la même base de données.

Stockage key-value chiffré

KeyValueStore.open accepte les mêmes options que FastSQL.connect, y compris encrypted et encryptionKey sur iOS et 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(),
});

Cela nécessite SQLCipher dans les builds natifs. La configuration, ainsi que les mesures à prendre en cas de perte de la clé après une restauration de sauvegarde, sont dans Comment chiffrer une base de données SQLite dans Capacitor.

@capgo/capacitor-data-storage-sqlite

@capgo/capacitor-data-storage-sqlite est un plugin clé-valeur dédié basé sur SQLite (initialement créé par Jean Pierre Quéau, maintenant maintenu par Capgo). Les valeurs sont des chaînes de caractères, et il ajoute quelques fonctionnalités : plusieurs tables par store, filtrage par préfixe et suffixe, ainsi que l’import et l’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 applique le filtre comme un motif SQL LIKE motif sur la clé (note% pour un préfixe, %note pour un suffixe, du texte brut pour une correspondance partielle). Le motif est inséré dans la chaîne SQL, ne transmettez donc que des filtres que vous contrôlez, jamais d’entrée utilisateur brute.

Sur le web, il utilise IndexedDB via localforage, que vous installez séparément :

bun add localforage

Concernant son chiffrement : openStore({ encrypted: true, mode: 'secret' }) utilise SQLCipher avec une phrase de passe issue de capacitor.config sous plugins.CapgoCapacitorDataStorageSqlite.encryptionSecret. Cette phrase de passe est incluse dans votre bundle d’application, et si vous ne la définissez pas, le plugin utilise une valeur par défaut intégrée. Considérez cela comme une protection contre une inspection de fichiers occasionnelle, pas contre quelqu’un qui décompresse votre application. Pour de vraies informations sensibles, utilisez une clé du Keychain ou du Keystore, comme dans l’exemple Fast SQL ci-dessus.

Fast SQL KeyValueStore ou data-storage-sqlite ?

  • Choisissez SQL rapide KeyValueStore si vous voulez des valeurs typées sans JSON manuel, des valeurs binaires, une clé de chiffrement au runtime, ou si vous utilisez déjà Fast SQL pour vos tables.
  • Choisissez data-storage-sqlite si vous avez besoin de ses fonctionnalités de table et de filtrage, de l'import/export JSON ou du support Electron.

Option 3 : Keychain et Keystore pour les secrets

Auth tokens, refresh tokens, API keys and database encryption keys belong in platform secure storage. @capgo/capacitor-native-biometric (8.6.0 et versions ultérieures) expose un API clé-valeur générique en s'appuyant sur l'iOS Keychain et 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' });

Gardez des valeurs de petite taille. Le plugin précise que le chiffrement adossé à Android Keystore fonctionne mieux en dessous d'environ 8 Ko par valeur. Si vous devez protéger beaucoup de données, stockez une clé aléatoire ici et utilisez-la pour chiffrer une base SQLite. Notre article sur le stockage sécurisé pour les tokens hors ligne approfondit la gestion des tokens.

N'oubliez pas que les éléments du Keychain sur iOS peuvent survivre à la désinstallation d'une app. Au premier lancement après une nouvelle installation, effacez les secrets obsolètes si votre app attend un état vierge (stockez un flag « premier lancement effectué » dans Preferences, qui est supprimé à la désinstallation, et videz les entrées du Keychain s'il est absent).

Choisir : une liste de décisions

  1. Est-ce un secret ? Token, mot de passe, code PIN, clé. Utilisez le stockage Keychain ou Keystore.
  2. S'agit-il de quelques petits paramètres ? Utilisez Preferences.
  3. S'agit-il de nombreuses entrées, d'un gros JSON, de données binaires ou d'un cache que vous souhaitez expirer et effacer par groupes ? Utilisez un store clé-valeur SQLite.
  4. Devez-vous le filtrer, le trier ou le joindre ? Ce ne sont plus des données clé-valeur. Créez une table, consultez le guide SQLite, ou d'utiliser une couche typée comme Kysely.

Migrer des données existantes entre les stores

Passer de localStorage ou Preferences vers un store SQLite est une copie unique au démarrage. Faites-l’avant que le reste de l'app ne lise les données, et enregistrez que l'opération a bien eu lieu.

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

Supprimez les secrets de l'ancien store dès qu'ils sont copiés dans un stockage sécurisé. Laisser un token dans Preferences après l'avoir déplacé vers le Keychain conserve l'exposition d'origine.

Comme ce code s'exécute en JavaScript, vous pouvez le déployer avec un Capgo live update. Le plugin SQLite est natif, la release qui l’ajoute nécessite donc un build store. Déployez d’abord le plugin, puis la migration code.

Troubleshooting

Les valeurs des préférences sont null après une mise à jour sur le web. L'origine a changé (port, nom d'hôte ou schéma), donc localStorage est un store différent et vide.

SQL rapide KeyValueStore.open génère une erreur sur iOS. Le canal HTTP local est bloqué. Ajoutez NSAllowsLocalNetworking vers Info.plist et recompilez l'app native.

get renvoie une chaîne là où vous attendiez un objet dans data-storage-sqlite. Ce plugin ne stocke que des chaînes de caractères. JSON.parse la valeur.

Secrets manquants après la restauration d'une sauvegarde sur un nouveau téléphone. Les clés Keystore et les éléments Keychain propres à l'appareil ne sont pas transférés vers un nouveau matériel. Considérez le stockage sécurisé comme un cache d'identifiants que vous pouvez récupérer en vous reconnectant.

Paramètres perdus après réinstallation, mais les tokens sont toujours là sur iOS. Attendu : Preferences est supprimé avec l’app, les éléments Keychain peuvent subsister. Utilisez le motif de flag de première exécution ci-dessus.

Résumé

  • Preferences : petits paramètres non sensibles.
  • Stockage clé-valeur SQLite (Fast SQL KeyValueStore ou @capgo/capacitor-data-storage-sqlite) : valeurs nombreuses ou volumineuses, cache, brouillons, chiffrement optionnel.
  • Keychain ou Keystore (@capgo/capacitor-native-biometric setData) : secrets et clés de chiffrement.

Références de plugins : Docs Fast SQL, documentation data-storage-sqlite, et le page du plugin biométrique natif.

Live updates pour les apps Capacitor

Lorsqu'un bug de la couche web est en ligne, déployez le correctif via Capgo au lieu d'attendre des jours l'approbation des stores. Les utilisateurs reçoivent la mise à jour en arrière-plan, tandis que les changements natifs suivent le circuit de validation habituel.

Les derniers articles du blog

Capgo vous offre les meilleures informations pour créer une application mobile vraiment professionnelle.