To use SQLite in a Capacitor app, install a native SQLite plugin such as @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.
必要な設定が少ない場合はSQLiteはオーバーキルです。リスト、レコード、フィルタ、ソート、同期したいデータを保存する場合は、正しいツールです。
SQLiteは適切なストレージの選択肢である場合
Capacitorアプリには、データを置くための複数の場所があります。各アプリは異なる問題を解決します:
| ストレージ | 適切な | 制限 |
|---|---|---|
localStorage / IndexedDB |
廃棄可能なキャッシュ | WebView内に存在し、OSがキャッシュを削除できるため、localStorageで実行されるクエリは存在しない |
@capacitor/preferences |
テーマ、言語などの少数の設定 | 文字列値のみ、大量のデータには適さず、暗号化されていない |
| キーチェーン/キーストアストレージ | トークン、暗号化キー | 小さな秘密 |
ファイル (@capacitor/filesystem) |
画像、PDF、エクスポート | クエリなし |
| SQLite | レコード、オフラインデータ、同期キュー、検索 | レコード、オフラインデータ、同期キュー、検索 |
管理スキーマとマイグレーション
Choosing a Capacitor SQLite plugin in 2026
There are three realistic options for native SQLite on Capacitor 8:
@capgo/capacitor-fast-sql |
@capacitor-community/sqlite |
@capgo/capacitor-data-storage-sqlite |
|
|---|---|---|---|
| Purpose | 目的 | SQLiteの一般的なデータベース | SQLiteの文字列キー値ストア |
| プラットフォーム | iOS、Android、Web | iOS、Android、Web、Electron | iOS、Android、Web、Electron |
| Webエンジン | 公式SQLite Wasm + OPFS | SQLite (sql.js + IndexedDB) | IndexedDB |
| データの輸送 | ローカルHTTPチャネル、ブリッジをバイパス | Capacitor ブリッジ | Capacitor ブリッジ |
| 暗号化 | iOS、Android用オプションのSQLCipher | SQLCipherは常にバンドル | SQLCipher、configファイル内のパスフレーズ |
| 行形式 | 列名でキー化されたオブジェクト | 列名でキー化されたオブジェクト | 文字列 |
| ORMサポート | カスタムドライバー(Kysely、Drizzle) | TypeORMの組み込みドライバー | なし |
このガイドではFast SQLを使用します。 Capgoによって管理され、Capacitorの主なバージョンを追跡 (プラグインv8はCapacitor8に対応)、SQLCipherのオプトインを維持し、ブリッジを通じて大きな結果セットをシリアライズしないようにしています。既存のコミュニティプラグインを実行している場合は、Capgoの 比較と移行ガイド をご覧ください。
Fast SQLをインストールする
bun add @capgo/capacitor-fast-sql
bunx cap sync
Fast SQLはCapacitorアプリで小さなHTTPサーバーを開始します localhost iOS: ループバックネットワークを許可する
に追加する
追加してください ios/App/App/Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
NSAllowsLocalNetworking App Transport Securityはローカルアドレスやループバックアドレスのみをカバーします。 実際のAPI呼び出しには、App Transport Securityを緩和しません。
__CAPGO_KEEP_0__はCapacitorアプリケーション
作成 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>
参照 android/app/src/main/AndroidManifest.xml:
<application
android:networkSecurityConfig="@xml/network_security_config"
...>
既存のネットワークセキュリティ設定 (SSLピンニングなど) があれば、代わりにそのブロックに追加するのではなく、別のファイルを作成するのではなく domain-config ファイルをオープンする
データベースの接続を管理するモジュールを1つ作成する
つまり、同じデータベースを繰り返しオープンするのは無駄であり、データベースのマイグレーションを実行する場所が1つになる
// 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 そして performancePresets バージョン 8.0.49 から利用可能です。 performancePresets も同時に有効にする foreign_keys、SQLite がデフォルトで省略しているため ON DELETE CASCADE 実際に機能します。
呼び出す getDb() CapacitorアプリでSQLiteを使用する方法
PRAGMA user_versionを使用したスキーママイグレーション
Fast SQLでは、raw SQLが提供され、migrationフレームワークは提供されないが、SQLiteはファイルヘッダにバージョンスロットを備えているため、問題ありません。 PRAGMA user_versionバージョンを保持するためのルール:
// 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}`);
});
}
}
1つのSQLステートメントごとに呼び出し。両方のネイティブ実装は、文字列内の最初のステートメントのみを準備するため、静かに1つを実行します。
- 各バージョンはトランザクション内で実行されるため、途中でクラッシュしてもデータベースは前のバージョンに戻り、ステップは次の起動時に再実行されます。
execute()SQLiteをCapacitorアプリで使用する"CREATE ...; CREATE ..."1つのSQLステートメントごとに呼び出し。両方のネイティブ実装は、文字列内の最初のステートメントのみを準備するため、静かに1つを実行します。 - 各バージョンはトランザクション内で実行されるため、途中でクラッシュしてもデータベースは前のバージョンに戻り、ステップは次の起動時に再実行されます。
- SQLiteは追加的な変更(新しいテーブル、新しい可空の列)をサポートします。
ALTER TABLE ... DROP COLUMNandRENAME COLUMN各バージョンはトランザクション内で実行されるため、途中でクラッシュしてもデータベースは前のバージョンに戻り、ステップは次の起動時に再実行されます。
マイグレーションとライブ更新
JavaScriptの変更を Capgoライブ更新codeマイグレーションはバンドルに含まれます。スキーマの変更はストアのリリースを必要としません。古いバンドルを新しいスキーマの上に置くことも可能です。マイグレーションはバックワード互換性を保つようにしてください (列を追加するだけではなく、使用を停止したときに列名を変更または削除しないでください)。
データの読み書き
SQLConnection 常に使用する4つのメソッドが得られます:
| メソッド | 戻り値 | 使用する |
|---|---|---|
query(sql, params) |
SQLRow[] |
SELECTとPRAGMAの読み取り |
run(sql, params) |
{ rowsAffected, insertId } |
INSERT、UPDATE、DELETE |
execute(sql, params) |
{ rows, rowsAffected, insertId } |
何でも、全結果 |
executeBatch(ops) |
SQLResult[] |
1 つのラウンド トリップで多くのステートメント |
常にユーザー入力値と置き換える値をバインドします。 ? ユーザー入力値から SQL を構築するのではなく、プレースホルダーと値をバインドすること
// 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;
}
行は列名でキー化されたプレーン オブジェクトとして返され、列と値のマッピングのステップが必要ありません。 1 and 0 と setPinned と変換します。
データ レイヤーのエッジで
トランザクションとバッチ ライト
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() 複数ステップの書き込みをトランザクションでラップする。データが一貫して保たれ、SQLite はコミットごとにディスクにsync するため、非常に高速になります。 beginTransaction(), commit() and rollback() コミットはコールバックが解決したときに発生し、例外が投げられたときにロールバックされます。必要な場合は、
すべての呼び出しは、1つのネイティブ接続を共有するデータベースごとに実行されます。トランザクションが開いている間、同じデータベースに書き込む他のcodeは、そのトランザクション内で実行されます。トランザクションを短くし、ネットワーク要求を待つことを避けましょう。
バイナリーデータの保存
パスを渡す Uint8Array そして、 Uint8Array バックに戻ります。プラグインは、データをトランスポートするためにbase64でエンコードし、実際の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;
大きなメディア(写真、動画)をファイルとして保存し、SQLiteにのみパスを保存してください。BLOBはサムネール、小さいアタッチメント、埋め込み用に適しています。
データベースの暗号化
Fast SQLはiOSとAndroidでSQLCipherをサポートしています。オプションなので、必要としないアプリは、
const db = await FastSQL.connect({
database: 'vault',
encrypted: true,
encryptionKey: keyFromSecureStorage,
});
Androidではアプリの net.zetetic:sqlcipher-android に追加します。iOSでは build.gradleiOS で、Pod を切り替えます。 CapgoCapacitorFastSql/SQLCipher SQLCipherの設定 How to encrypt a SQLite database in Capacitor__CAPGO_KEEP_0__
ウェブ上での暗号化
ウェブ上でのサポート @sqlite.org/sqlite-wasm ウェブ上での高速 SQL
ウェブ上でのファイルの保存
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
OPFS の必要条件
// 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'],
},
});
Vite の場合 CapgoCapacitorFastSql.configureWeb({ useOpfs, worker }) ヘッダーが欠如している場合 connect()初回実行前にデフォルト値を変更する
require-corp ネイティブでは無視される
データベースファイルの保存場所
| プラットフォーム | ロケーション | ノート |
|---|---|---|
| iOS | Documents/<name>.db |
iCloudとデバイスのバックアップにデフォルトで含まれます |
| Android | files/<name>.db アプリの内部ストレージ |
Android Auto Backupのルールに従います |
| Web | OPFSのページオリジン | サイトデータをクリアすると削除されます |
2つの実用的な結果があります。最初のものは、アンインストールすると、両方のモバイルプラットフォームでファイルが削除されます。2番目のものは、バックアップが新しいデバイスにファイルを復元できることです。通常、これが望ましいことですが、バックアップに移動しない暗号化キーで暗号化されたデータベースファイルが復元された場合、復元されたファイルが開かれません。暗号化ガイドでは、そのような場合にどう対応するかについて説明しています。
パフォーマンスチェックリスト
- オンにします
walMode. 書き手の視線を遮る読者。 - トランザクション内でバッチ書きを行う
executeBatch. インポートと同期のための単一の大きな勝利です。 - フィルタリングまたはソートする列にインデックスを追加し、次に
EXPLAIN QUERY PLAN. - 必要な列のみを選択します。 大きな
TEXT、BLOB列は、HTTP チャネルを超えても、コストと転送時間を増やします。 - ページネーションに
WHERE created_at < ? ORDER BY created_at DESC LIMIT 50を使用するのではなく、大きなOFFSET値を使用するのではなく - データを大量に削除した後は
VACUUM時々実行してファイルを縮小する。データベース全体を書き直すので、アイドル時間帯に実行する。
トラブルシューティング
SQL execution failed iOSでは接続直後に実行する。 ローカルHTTP要求がブロックされました。確認してください NSAllowsLocalNetworking が正しい Info.plist そしてネイティブアプリを再構築する。ライブリロードだけでは十分ではありません。
CLEARTEXT communication to localhost not permitted Androidでは。 ネットワークセキュリティ設定が欠落しているか、またはマニフェストから参照されていない。
最初のステートメントだけが実行された。 複数のステートメントを1つの文字列に渡しました。分割するか、 executeBatch.
no such table アップデート後も マイグレーションが実行されなかった、または別のデータベース名で実行された。ログ PRAGMA user_version 起動時にログして、ステップ配列と比較する
Android上では、論理値または計算値に基づくフィルタはマッチしない 読み取りクエリの場合、Android実装ではパラメータをテキストとしてバインドする。INTEGER親和性を持つ列と比較する場合、テキストに変換されるため比較は機能するが、式 (例えば) json_extract(...) = ? または count(*) > ?Android上では、式と比較する場合、テキストと数値を比較するのと同じようにテキストを数値に変換する 1/0 instead of true/false代わりに CAST(? AS INTEGER) テキストと数値を比較するのと同じようにテキストを数値に変換する
INSERT ... RETURNING Android上では、式と比較する場合、テキストと数値を比較するのと同じようにテキストを数値に変換する Android上では、式と比較する場合、テキストと数値を比較するのと同じようにテキストを数値に変換する SELECT, PRAGMA または EXPLAIN. Use insertId from run() と選択する WITHの後 SELECT同じことが、
: のクエリにも当てはまります。
Encryption is not available in this build を始まるように 、または encrypted: true をテストする
ブラウザでリロードするとデータが消える
OPFS が利用できないのは、ほとんどの場合、展開されたホストに COOP/COEP ヘッダーが欠けているためです。
- KyselyとCapacitorを使用してSQLiteを実装: __CAPGO_KEEP_0__とFast SQLのための小さなカスタムドライバーを持つタイプ化されたクエリビルダー。
- Drizzle ORM と Capacitor を使用して SQLite を実装: __CAPGO_KEEP_0__とSQLiteのためのスキーマの作成、TypeScriptでの生成されたマイグレーション。
- TypeORMとCapacitorを使用してSQLiteを実装: __CAPGO_KEEP_0__とSQLiteのためのデコレータとリポジトリ。
- Capacitorのキーバリューストレージオプション: __CAPGO_KEEP_0__で、PreferencesまたはSQLite上のKVテーブルが十分です。
プラグインのリファレンスは Fast SQLドキュメント とともに そして. SQL に必要なのは、追加したり設定を変更したときに、ネイティブの再構築です。 そのようなことを維持したくない場合は、Mac を維持する必要があります。 Capgo ビルド クラウドで署名された iOS と Android のバイナリを生成できます。 2026 年 4 月以降、App Store へのアップロードは Xcode 26 またはそれ以降でビルドする必要があります。 したがって、次のリリース前にビルドマシンを確認してください。