A two-panel todo app demonstrating tauri-plugin-libsql with local SQLite and optional Turso embedded replica sync.
- Left panel — local SQLite database (
todos.db) with instant reads/writes - Right panel — Turso embedded replica (
todos-turso.db) synced with a remote Turso database; demonstrates real network latency on writes - Drizzle ORM via
createDrizzleProxy(sqlite-proxy pattern) - Browser-safe migrations with
migrate()bundled at build time viaimport.meta.glob - Sonner toast notifications per-panel (bottom-left for local, bottom-right for Turso)
- Optional AES-256 encryption via environment variable (local DB only)
bun installbun run tauri devLIBSQL_ENCRYPTION_KEY=my-secret-key bun run tauri devThe key is padded/truncated to 32 bytes automatically.
The right panel shows a connect form on first launch. Enter your Turso credentials:
- Remote URL —
libsql://mydb-org.turso.io - Auth Token — your Turso auth token
# Get your credentials
turso db show --url <db-name>
turso db tokens create <db-name>Credentials are saved in localStorage and restored on next launch. Click the × button in the Turso panel header to disconnect.
Each panel has a Reset DB button at the bottom. It drops the todos table and the __drizzle_migrations tracking table, then re-runs all migrations from scratch. Useful when:
- You manually deleted tables in the Turso console
- The migration tracking table has stale records
- You want a clean slate without deleting the DB file
Both database files are created in the current working directory when you run the command. Running from examples/todo-list/ puts them there.
todos.db— local SQLitetodos-turso.db— embedded replica (Turso sync)
The two databases must be separate files. Opening a plain SQLite file as an embedded replica (or vice versa) causes a "metadata file missing" error.
App.svelte
│
├── <TodoList dbFile="todos.db" position="bottom-left" />
│ └── Database.load() → migrate() → Drizzle queries
│
└── <TodoList dbFile="todos-turso.db" syncUrl="..." position="bottom-right" />
└── Database.load({ syncUrl, authToken }) ← initial sync from Turso
migrate()
Drizzle queries
db.sync() ← manual pull of remote changes
// 1. Open/create the database file (initial Turso sync if syncUrl set)
dbInstance = await Database.load(options);
// 2. Run pending migrations
await migrate(dbPath, migrations);
// 3. Now safe to query
await loadTodos();drizzle/
0000_init.sql ← generated by drizzle-kit, bundled by Vite at build time
# 1. Edit src/lib/schema.ts
# 2. Generate a new migration
bun run db:generate
# 3. Restart the app — migration runs automatically on next launchexamples/todo-list/
├── drizzle/ # Generated SQL migration files
├── src/
│ ├── App.svelte # Two-panel layout, Turso connect/disconnect form
│ └── lib/
│ ├── TodoList.svelte # Self-contained panel: DB lifecycle, CRUD, sync
│ ├── schema.ts # Drizzle table definitions
│ ├── db.ts # createDb() factory for Drizzle instance
│ └── components/ # UI primitives (Button, Input, Card, etc.)
├── src-tauri/
│ ├── Cargo.toml # Plugin with replication feature enabled
│ └── src/lib.rs # Plugin registration, encryption from env
├── drizzle.config.ts # drizzle-kit config
└── package.json # Scripts: db:generate, tauri dev/build