This tutorial demonstrates how to build production-ready applications using @capgo/capacitor-fast-sql. You'll learn to create a robust database service, implement sync systems, handle migrations, and optimize for performance.
npm install @capgo/capacitor-fast-sql
npx cap sync
Add to ios/App/App/Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
Add to android/app/src/main/AndroidManifest.xml:
<application
android:usesCleartextTraffic="true">
...
</application>
Create a comprehensive database service that handles connection management, migrations, and common operations:
import { FastSQL, IsolationLevel } from '@capgo/capacitor-fast-sql';
export interface DatabaseConfig {
name: string;
version: number;
encrypted?: boolean;
encryptionKey?: string;
}
export interface Migration {
version: number;
up: (db: any) => Promise<void>;
down?: (db: any) => Promise<void>;
}
export class DatabaseService {
private db: any = null;
private config: DatabaseConfig;
private migrations: Migration[] = [];
private isInitialized = false;
constructor(config: DatabaseConfig) {
this.config = config;
}
/**
* Register migrations to run during initialization
*/
registerMigrations(migrations: Migration[]): void {
this.migrations = migrations.sort((a, b) => a.version - b.version);
}
/**
* Initialize database and run migrations
*/
async initialize(): Promise<void> {
if (this.isInitialized) {
console.log('Database already initialized');
return;
}
try {
// Connect to database
this.db = await FastSQL.connect({
database: this.config.name,
encrypted: this.config.encrypted,
encryptionKey: this.config.encryptionKey,
});
console.log('Database connected on port:', this.db.port);
// Create migrations table
await this.createMigrationsTable();
// Run pending migrations
await this.runMigrations();
this.isInitialized = true;
console.log('Database initialized successfully');
} catch (error) {
console.error('Failed to initialize database:', error);
throw error;
}
}
/**
* Create migrations tracking table
*/
private async createMigrationsTable(): Promise<void> {
await this.db.execute(`
CREATE TABLE IF NOT EXISTS migrations (
version INTEGER PRIMARY KEY,
applied_at INTEGER DEFAULT (strftime('%s', 'now'))
)
`);
}
/**
* Get current database version
*/
private async getCurrentVersion(): Promise<number> {
const result = await this.db.query(
'SELECT MAX(version) as version FROM migrations'
);
return result[0]?.version || 0;
}
/**
* Run all pending migrations
*/
private async runMigrations(): Promise<void> {
const currentVersion = await this.getCurrentVersion();
const pendingMigrations = this.migrations.filter(
(m) => m.version > currentVersion
);
if (pendingMigrations.length === 0) {
console.log('No pending migrations');
return;
}
console.log(`Running ${pendingMigrations.length} migrations...`);
for (const migration of pendingMigrations) {
try {
await this.db.transaction(async (tx: any) => {
console.log(`Applying migration ${migration.version}...`);
await migration.up(tx);
await tx.run('INSERT INTO migrations (version) VALUES (?)', [
migration.version,
]);
});
console.log(`Migration ${migration.version} applied successfully`);
} catch (error) {
console.error(`Migration ${migration.version} failed:`, error);
throw error;
}
}
}
/**
* Execute a query with parameters
*/
async query<T = any>(
statement: string,
params: any[] = []
): Promise<T[]> {
this.ensureInitialized();
return await this.db.query(statement, params);
}
/**
* Execute a statement that modifies data
*/
async run(
statement: string,
params: any[] = []
): Promise<{ insertId?: number; rowsAffected: number }> {
this.ensureInitialized();
return await this.db.run(statement, params);
}
/**
* Execute multiple statements in a transaction
*/
async transaction<T>(
callback: (tx: any) => Promise<T>,
isolationLevel?: IsolationLevel
): Promise<T> {
this.ensureInitialized();
return await this.db.transaction(callback, isolationLevel);
}
/**
* Execute batch operations efficiently
*/
async executeBatch(
operations: Array<{ statement: string; params?: any[] }>
): Promise<any[]> {
this.ensureInitialized();
return await this.db.executeBatch(operations);
}
/**
* Get direct HTTP server info for maximum performance
*/
async getServerInfo(): Promise<{ port: number; token: string }> {
this.ensureInitialized();
return await FastSQL.getServerInfo({ database: this.config.name });
}
/**
* Close database connection
*/
async close(): Promise<void> {
if (!this.isInitialized) return;
await FastSQL.disconnect(this.config.name);
this.db = null;
this.isInitialized = false;
console.log('Database closed');
}
/**
* Ensure database is initialized
*/
private ensureInitialized(): void {
if (!this.isInitialized) {
throw new Error('Database not initialized. Call initialize() first.');
}
}
/**
* Vacuum database to reclaim space
*/
async vacuum(): Promise<void> {
this.ensureInitialized();
await this.db.execute('VACUUM');
}
/**
* Get database size in bytes
*/
async getDatabaseSize(): Promise<number> {
this.ensureInitialized();
const result = await this.db.query('PRAGMA page_count');
const pageCount = result[0].page_count;
const pageSizeResult = await this.db.query('PRAGMA page_size');
const pageSize = pageSizeResult[0].page_size;
return pageCount * pageSize;
}
}
Define migrations for your database schema:
import { Migration } from './DatabaseService';
export const migrations: Migration[] = [
// Migration 1: Initial schema
{
version: 1,
up: async (db) => {
await db.execute(`
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
)
`);
await db.execute(`
CREATE INDEX idx_users_email ON users(email)
`);
await db.execute(`
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT,
published BOOLEAN DEFAULT 0,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
updated_at INTEGER DEFAULT (strftime('%s', 'now')),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
`);
await db.execute(`
CREATE INDEX idx_posts_user_id ON posts(user_id)
`);
},
down: async (db) => {
await db.execute('DROP TABLE IF EXISTS posts');
await db.execute('DROP TABLE IF EXISTS users');
},
},
// Migration 2: Add sync tracking
{
version: 2,
up: async (db) => {
await db.execute(`
CREATE TABLE sync_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_name TEXT NOT NULL,
record_id INTEGER NOT NULL,
operation TEXT NOT NULL,
synced BOOLEAN DEFAULT 0,
created_at INTEGER DEFAULT (strftime('%s', 'now'))
)
`);
await db.execute(`
CREATE INDEX idx_sync_log_synced ON sync_log(synced)
`);
// Add sync columns to existing tables
await db.execute(`
ALTER TABLE users ADD COLUMN sync_version INTEGER DEFAULT 1
`);
await db.execute(`
ALTER TABLE posts ADD COLUMN sync_version INTEGER DEFAULT 1
`);
},
},
// Migration 3: Add comments table
{
version: 3,
up: async (db) => {
await db.execute(`
CREATE TABLE comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
post_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
content TEXT NOT NULL,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
updated_at INTEGER DEFAULT (strftime('%s', 'now')),
sync_version INTEGER DEFAULT 1,
FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
`);
await db.execute(`
CREATE INDEX idx_comments_post_id ON comments(post_id)
`);
},
},
];
Complete blog app with posts, comments, and user management:
import { DatabaseService } from './DatabaseService';
import { migrations } from './migrations';
export interface User {
id?: number;
email: string;
name: string;
created_at?: number;
updated_at?: number;
}
export interface Post {
id?: number;
user_id: number;
title: string;
content: string;
published: boolean;
created_at?: number;
updated_at?: number;
}
export interface Comment {
id?: number;
post_id: number;
user_id: number;
content: string;
created_at?: number;
updated_at?: number;
}
export class BlogApp {
private db: DatabaseService;
constructor() {
this.db = new DatabaseService({
name: 'blog',
version: 3,
});
this.db.registerMigrations(migrations);
}
async initialize(): Promise<void> {
await this.db.initialize();
}
// User operations
async createUser(user: User): Promise<number> {
const result = await this.db.run(
'INSERT INTO users (email, name) VALUES (?, ?)',
[user.email, user.name]
);
return result.insertId!;
}
async getUserByEmail(email: string): Promise<User | null> {
const users = await this.db.query<User>(
'SELECT * FROM users WHERE email = ?',
[email]
);
return users[0] || null;
}
async updateUser(id: number, updates: Partial<User>): Promise<void> {
const fields = Object.keys(updates)
.map((key) => `${key} = ?`)
.join(', ');
const values = [...Object.values(updates), id];
await this.db.run(
`UPDATE users SET ${fields}, updated_at = strftime('%s', 'now') WHERE id = ?`,
values
);
}
// Post operations
async createPost(post: Post): Promise<number> {
const result = await this.db.run(
'INSERT INTO posts (user_id, title, content, published) VALUES (?, ?, ?, ?)',
[post.user_id, post.title, post.content, post.published ? 1 : 0]
);
return result.insertId!;
}
async getPostsByUser(userId: number): Promise<Post[]> {
return await this.db.query<Post>(
'SELECT * FROM posts WHERE user_id = ? ORDER BY created_at DESC',
[userId]
);
}
async getPublishedPosts(limit = 20, offset = 0): Promise<Post[]> {
return await this.db.query<Post>(
`SELECT p.*, u.name as author_name
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.published = 1
ORDER BY p.created_at DESC
LIMIT ? OFFSET ?`,
[limit, offset]
);
}
async updatePost(id: number, updates: Partial<Post>): Promise<void> {
const fields = Object.keys(updates)
.map((key) => `${key} = ?`)
.join(', ');
const values = [...Object.values(updates), id];
await this.db.run(
`UPDATE posts SET ${fields}, updated_at = strftime('%s', 'now') WHERE id = ?`,
values
);
}
async deletePost(id: number): Promise<void> {
await this.db.run('DELETE FROM posts WHERE id = ?', [id]);
}
// Comment operations
async createComment(comment: Comment): Promise<number> {
const result = await this.db.run(
'INSERT INTO comments (post_id, user_id, content) VALUES (?, ?, ?)',
[comment.post_id, comment.user_id, comment.content]
);
return result.insertId!;
}
async getCommentsByPost(postId: number): Promise<Comment[]> {
return await this.db.query<Comment>(
`SELECT c.*, u.name as author_name
FROM comments c
JOIN users u ON c.user_id = u.id
WHERE c.post_id = ?
ORDER BY c.created_at ASC`,
[postId]
);
}
async deleteComment(id: number): Promise<void> {
await this.db.run('DELETE FROM comments WHERE id = ?', [id]);
}
// Search
async searchPosts(query: string): Promise<Post[]> {
return await this.db.query<Post>(
`SELECT p.*, u.name as author_name
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.published = 1
AND (p.title LIKE ? OR p.content LIKE ?)
ORDER BY p.created_at DESC`,
[`%${query}%`, `%${query}%`]
);
}
// Statistics
async getStats() {
const [users, posts, comments] = await Promise.all([
this.db.query('SELECT COUNT(*) as count FROM users'),
this.db.query('SELECT COUNT(*) as count FROM posts WHERE published = 1'),
this.db.query('SELECT COUNT(*) as count FROM comments'),
]);
return {
totalUsers: users[0].count,
publishedPosts: posts[0].count,
totalComments: comments[0].count,
};
}
async close(): Promise<void> {
await this.db.close();
}
}
Implement a robust sync system for CRDTs and local-first applications:
import { DatabaseService } from './DatabaseService';
export interface SyncOperation {
id?: number;
table_name: string;
record_id: number;
operation: 'INSERT' | 'UPDATE' | 'DELETE';
data?: any;
synced: boolean;
created_at?: number;
}
export class SyncEngine {
private db: DatabaseService;
private syncInterval: number = 30000; // 30 seconds
private intervalId: any = null;
private isSyncing = false;
constructor(db: DatabaseService) {
this.db = db;
}
/**
* Start automatic sync
*/
startAutoSync(): void {
if (this.intervalId) return;
this.intervalId = setInterval(async () => {
await this.sync();
}, this.syncInterval);
// Initial sync
this.sync();
}
/**
* Stop automatic sync
*/
stopAutoSync(): void {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
/**
* Manually trigger sync
*/
async sync(): Promise<{ uploaded: number; downloaded: number }> {
if (this.isSyncing) {
console.log('Sync already in progress');
return { uploaded: 0, downloaded: 0 };
}
this.isSyncing = true;
console.log('Starting sync...');
try {
// Upload local changes
const uploaded = await this.uploadChanges();
// Download remote changes
const downloaded = await this.downloadChanges();
console.log(`Sync complete: ${uploaded} uploaded, ${downloaded} downloaded`);
return { uploaded, downloaded };
} catch (error) {
console.error('Sync failed:', error);
throw error;
} finally {
this.isSyncing = false;
}
}
/**
* Log a change for sync
*/
async logChange(
tableName: string,
recordId: number,
operation: 'INSERT' | 'UPDATE' | 'DELETE',
data?: any
): Promise<void> {
await this.db.run(
'INSERT INTO sync_log (table_name, record_id, operation, data) VALUES (?, ?, ?, ?)',
[tableName, recordId, operation, JSON.stringify(data)]
);
}
/**
* Upload pending changes to server
*/
private async uploadChanges(): Promise<number> {
// Get unsynced operations
const operations = await this.db.query<SyncOperation>(
'SELECT * FROM sync_log WHERE synced = 0 ORDER BY created_at ASC'
);
if (operations.length === 0) {
return 0;
}
try {
// Send to server (implement your API call here)
await this.sendToServer(operations);
// Mark as synced
const ids = operations.map((op) => op.id);
await this.db.run(
`UPDATE sync_log SET synced = 1 WHERE id IN (${ids.join(',')})`,
[]
);
return operations.length;
} catch (error) {
console.error('Upload failed:', error);
throw error;
}
}
/**
* Download changes from server
*/
private async downloadChanges(): Promise<number> {
try {
// Get latest sync version
const result = await this.db.query(
'SELECT MAX(sync_version) as version FROM users'
);
const currentVersion = result[0]?.version || 0;
// Fetch from server (implement your API call here)
const remoteChanges = await this.fetchFromServer(currentVersion);
// Apply changes in transaction
await this.db.transaction(async (tx) => {
for (const change of remoteChanges) {
await this.applyRemoteChange(tx, change);
}
});
return remoteChanges.length;
} catch (error) {
console.error('Download failed:', error);
throw error;
}
}
/**
* Send operations to server
*/
private async sendToServer(operations: SyncOperation[]): Promise<void> {
// Implement your server API call here
// Example:
// const response = await fetch('https://api.example.com/sync/upload', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ operations })
// });
// if (!response.ok) throw new Error('Upload failed');
console.log('Sending to server:', operations);
}
/**
* Fetch changes from server
*/
private async fetchFromServer(afterVersion: number): Promise<any[]> {
// Implement your server API call here
// Example:
// const response = await fetch(`https://api.example.com/sync/download?after=${afterVersion}`);
// if (!response.ok) throw new Error('Download failed');
// return await response.json();
console.log('Fetching from server after version:', afterVersion);
return [];
}
/**
* Apply a remote change to local database
*/
private async applyRemoteChange(tx: any, change: any): Promise<void> {
const { table, operation, data } = change;
switch (operation) {
case 'INSERT':
case 'UPDATE':
await this.upsertRecord(tx, table, data);
break;
case 'DELETE':
await tx.run(`DELETE FROM ${table} WHERE id = ?`, [data.id]);
break;
}
}
/**
* Insert or update a record
*/
private async upsertRecord(tx: any, table: string, data: any): Promise<void> {
const fields = Object.keys(data).filter((k) => k !== 'id');
const placeholders = fields.map(() => '?').join(',');
const updates = fields.map((f) => `${f} = excluded.${f}`).join(',');
await tx.run(
`INSERT INTO ${table} (${fields.join(',')}) VALUES (${placeholders})
ON CONFLICT(id) DO UPDATE SET ${updates}`,
fields.map((f) => data[f])
);
}
/**
* Get sync status
*/
async getSyncStatus(): Promise<{
pending: number;
lastSync?: number;
}> {
const pending = await this.db.query(
'SELECT COUNT(*) as count FROM sync_log WHERE synced = 0'
);
const lastSync = await this.db.query(
'SELECT MAX(created_at) as last_sync FROM sync_log WHERE synced = 1'
);
return {
pending: pending[0].count,
lastSync: lastSync[0].last_sync,
};
}
}
import { DatabaseService } from './DatabaseService';
export interface Product {
id?: number;
name: string;
description: string;
price: number;
stock: number;
image_url?: string;
}
export interface CartItem {
id?: number;
product_id: number;
quantity: number;
price_at_add: number;
product?: Product;
}
export interface Order {
id?: number;
total: number;
status: 'pending' | 'completed' | 'cancelled';
created_at?: number;
}
export class ShoppingCartApp {
private db: DatabaseService;
constructor() {
this.db = new DatabaseService({
name: 'shop',
version: 1,
});
}
async initialize(): Promise<void> {
await this.db.initialize();
// Create tables
await this.db.transaction(async (tx) => {
await tx.execute(`
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
price REAL NOT NULL,
stock INTEGER DEFAULT 0,
image_url TEXT
)
`);
await tx.execute(`
CREATE TABLE IF NOT EXISTS cart_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
price_at_add REAL NOT NULL,
FOREIGN KEY (product_id) REFERENCES products(id)
)
`);
await tx.execute(`
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
total REAL NOT NULL,
status TEXT DEFAULT 'pending',
created_at INTEGER DEFAULT (strftime('%s', 'now'))
)
`);
await tx.execute(`
CREATE TABLE IF NOT EXISTS order_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
price REAL NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
)
`);
});
}
// Cart operations
async addToCart(productId: number, quantity: number): Promise<void> {
// Get product price
const products = await this.db.query<Product>(
'SELECT * FROM products WHERE id = ?',
[productId]
);
if (products.length === 0) {
throw new Error('Product not found');
}
const product = products[0];
if (product.stock! < quantity) {
throw new Error('Insufficient stock');
}
// Check if item already in cart
const existing = await this.db.query<CartItem>(
'SELECT * FROM cart_items WHERE product_id = ?',
[productId]
);
if (existing.length > 0) {
// Update quantity
await this.db.run(
'UPDATE cart_items SET quantity = quantity + ? WHERE product_id = ?',
[quantity, productId]
);
} else {
// Add new item
await this.db.run(
'INSERT INTO cart_items (product_id, quantity, price_at_add) VALUES (?, ?, ?)',
[productId, quantity, product.price]
);
}
}
async getCart(): Promise<CartItem[]> {
return await this.db.query<CartItem>(
`SELECT ci.*, p.name, p.description, p.image_url
FROM cart_items ci
JOIN products p ON ci.product_id = p.id`
);
}
async updateCartItem(itemId: number, quantity: number): Promise<void> {
await this.db.run('UPDATE cart_items SET quantity = ? WHERE id = ?', [
quantity,
itemId,
]);
}
async removeFromCart(itemId: number): Promise<void> {
await this.db.run('DELETE FROM cart_items WHERE id = ?', [itemId]);
}
async clearCart(): Promise<void> {
await this.db.run('DELETE FROM cart_items', []);
}
async getCartTotal(): Promise<number> {
const result = await this.db.query(
'SELECT SUM(quantity * price_at_add) as total FROM cart_items'
);
return result[0]?.total || 0;
}
// Checkout
async checkout(): Promise<number> {
return await this.db.transaction(async (tx) => {
// Get cart items
const cartItems = await tx.query<CartItem>(
'SELECT * FROM cart_items'
);
if (cartItems.length === 0) {
throw new Error('Cart is empty');
}
// Calculate total
const total = cartItems.reduce(
(sum, item) => sum + item.quantity * item.price_at_add,
0
);
// Create order
const orderResult = await tx.run(
'INSERT INTO orders (total, status) VALUES (?, ?)',
[total, 'pending']
);
const orderId = orderResult.insertId!;
// Create order items and update stock
for (const item of cartItems) {
await tx.run(
'INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)',
[orderId, item.product_id, item.quantity, item.price_at_add]
);
await tx.run(
'UPDATE products SET stock = stock - ? WHERE id = ?',
[item.quantity, item.product_id]
);
}
// Clear cart
await tx.run('DELETE FROM cart_items', []);
return orderId;
});
}
async getOrders(): Promise<Order[]> {
return await this.db.query<Order>(
'SELECT * FROM orders ORDER BY created_at DESC'
);
}
async close(): Promise<void> {
await this.db.close();
}
}
import React, { useEffect, useState } from 'react';
import { BlogApp, Post } from './BlogApp';
const db = new BlogApp();
export const BlogPostList: React.FC = () => {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadPosts = async () => {
await db.initialize();
const allPosts = await db.getPublishedPosts();
setPosts(allPosts);
setLoading(false);
};
loadPosts();
}, []);
if (loading) return <div>Loading...</div>;
return (
<div>
<h1>Blog Posts</h1>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
<small>By {(post as any).author_name}</small>
</article>
))}
</div>
);
};
<template>
<div>
<h1>Blog Posts</h1>
<div v-if="loading">Loading...</div>
<article v-for="post in posts" :key="post.id">
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
<small>By {{ post.author_name }}</small>
</article>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { BlogApp, Post } from './BlogApp';
const db = new BlogApp();
const posts = ref<Post[]>([]);
const loading = ref(true);
onMounted(async () => {
await db.initialize();
posts.value = await db.getPublishedPosts();
loading.value = false;
});
</script>
// Create indexes for frequently queried columns
await db.execute('CREATE INDEX idx_posts_user_id ON posts(user_id)');
await db.execute('CREATE INDEX idx_posts_published ON posts(published)');
// Much faster than individual inserts
await db.transaction(async (tx) => {
for (const item of items) {
await tx.run('INSERT INTO items (name, value) VALUES (?, ?)', [
item.name,
item.value,
]);
}
});
const operations = items.map((item) => ({
statement: 'INSERT INTO items (name, value) VALUES (?, ?)',
params: [item.name, item.value],
}));
await db.executeBatch(operations);
const { port, token } = await db.getServerInfo();
const response = await fetch(`http://localhost:${port}/execute`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
statement: 'SELECT * FROM large_table',
params: [],
}),
});
const result = await response.json();
Solution: Use transactions properly and avoid long-running queries:
// Bad - multiple operations without transaction
await db.run('INSERT INTO users ...');
await db.run('UPDATE posts ...');
// Good - use transaction
await db.transaction(async (tx) => {
await tx.run('INSERT INTO users ...');
await tx.run('UPDATE posts ...');
});
Solution: Add indexes and use EXPLAIN QUERY PLAN:
const plan = await db.query('EXPLAIN QUERY PLAN SELECT * FROM posts WHERE user_id = ?', [123]);
console.log('Query plan:', plan);
// Add missing indexes
await db.execute('CREATE INDEX idx_posts_user_id ON posts(user_id)');
Solution: Run VACUUM periodically:
await db.vacuum();
const size = await db.getDatabaseSize();
console.log('Database size:', size, 'bytes');
You now have a complete understanding of building production-ready applications with @capgo/capacitor-fast-sql:
The custom HTTP protocol provides exceptional performance for sync systems and large datasets, making it ideal for local-first applications and IndexedDB replacements.