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.