Turso
Build with Turso as an edge SQLite database. Use this skill when the project
You are a database specialist who integrates Turso into projects. Turso is an edge-hosted SQLite database built on libSQL, offering low-latency reads via distributed replicas, embedded replicas for zero-latency local reads, and per-tenant database isolation. ## Key Points - Use embedded replicas for read-heavy workloads — zero-latency local reads - Use `batch()` for multiple related writes — they execute atomically - Use per-tenant databases for SaaS multi-tenancy — true isolation - Place databases in regions close to your users - Sync embedded replicas on a reasonable interval (30-60s) - Use parameterized queries — never interpolate user input into SQL strings - Using remote reads when embedded replicas would eliminate latency - Not using batch mode for related writes — they should be atomic - Storing large blobs in SQLite — use object storage instead - Creating indexes after data grows large — define them upfront - Not calling `db.sync()` after writes when using embedded replicas with reads - Using SQLite-specific syntax that breaks portability if you might switch databases ## Quick Example ```bash npm install @libsql/client # Or with Drizzle npm install @libsql/client drizzle-orm ```
skilldb get database-services-skills/TursoFull skill: 201 linesTurso Edge SQLite Integration
You are a database specialist who integrates Turso into projects. Turso is an edge-hosted SQLite database built on libSQL, offering low-latency reads via distributed replicas, embedded replicas for zero-latency local reads, and per-tenant database isolation.
Core Philosophy
SQLite at the edge
Turso distributes SQLite databases across edge locations. Reads are fast because the data is close to the user. Writes go to the primary and propagate to replicas.
Embedded replicas
Turso can sync a full copy of the database into your application process. Reads hit local SQLite — zero network latency. Writes sync back to the primary. This is the killer feature for read-heavy workloads.
One database per tenant
Turso supports thousands of databases per account. Instead of row-level tenancy, you can give each tenant their own database — true isolation with no noisy-neighbor concerns.
Setup
Install
npm install @libsql/client
# Or with Drizzle
npm install @libsql/client drizzle-orm
Connect (remote)
import { createClient } from '@libsql/client';
const db = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});
Connect (embedded replica)
import { createClient } from '@libsql/client';
const db = createClient({
url: 'file:local-replica.db', // Local SQLite file
syncUrl: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
syncInterval: 60, // Sync every 60 seconds
});
// Manual sync
await db.sync();
With Drizzle ORM
import { createClient } from '@libsql/client';
import { drizzle } from 'drizzle-orm/libsql';
import * as schema from './schema';
const client = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});
const db = drizzle(client, { schema });
Key Techniques
Queries
// Select
const result = await db.execute({
sql: 'SELECT * FROM posts WHERE status = ? ORDER BY created_at DESC LIMIT ?',
args: ['published', 20],
});
const posts = result.rows;
// Insert
const result = await db.execute({
sql: 'INSERT INTO posts (title, content, author_id) VALUES (?, ?, ?) RETURNING *',
args: ['Hello', '...', userId],
});
const post = result.rows[0];
// Update
await db.execute({
sql: 'UPDATE posts SET title = ?, updated_at = datetime() WHERE id = ?',
args: ['Updated', postId],
});
// Delete
await db.execute({
sql: 'DELETE FROM posts WHERE id = ?',
args: [postId],
});
// Batch (atomic)
await db.batch([
{ sql: 'UPDATE posts SET status = ? WHERE id = ?', args: ['published', postId] },
{ sql: 'INSERT INTO notifications (user_id, type) VALUES (?, ?)', args: [userId, 'published'] },
], 'write');
// Transaction
const tx = await db.transaction('write');
try {
await tx.execute({ sql: 'UPDATE accounts SET balance = balance - ? WHERE id = ?', args: [100, fromId] });
await tx.execute({ sql: 'UPDATE accounts SET balance = balance + ? WHERE id = ?', args: [100, toId] });
await tx.commit();
} catch {
await tx.rollback();
}
Multi-tenant databases
# Create a database per tenant
turso db create tenant-acme --group default
turso db create tenant-globex --group default
# All databases in a group share the same schema
# Apply migrations to the group's schema database
function getDbForTenant(tenantId: string) {
return createClient({
url: `libsql://${tenantId}-myorg.turso.io`,
authToken: process.env.TURSO_AUTH_TOKEN!,
});
}
const db = getDbForTenant('acme');
const posts = await db.execute('SELECT * FROM posts');
Schema migrations
# Create database
turso db create myapp
# Apply schema via CLI
turso db shell myapp < schema.sql
# Or via the client
await db.executeMultiple(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
email TEXT NOT NULL UNIQUE,
name TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS posts (
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
title TEXT NOT NULL,
content TEXT NOT NULL,
author_id TEXT NOT NULL REFERENCES users(id),
status TEXT NOT NULL DEFAULT 'draft',
created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_posts_author ON posts(author_id);
CREATE INDEX IF NOT EXISTS idx_posts_status ON posts(status, created_at);
`);
Best Practices
- Use embedded replicas for read-heavy workloads — zero-latency local reads
- Use
batch()for multiple related writes — they execute atomically - Use per-tenant databases for SaaS multi-tenancy — true isolation
- Place databases in regions close to your users
- Sync embedded replicas on a reasonable interval (30-60s)
- Use parameterized queries — never interpolate user input into SQL strings
Anti-Patterns
- Using remote reads when embedded replicas would eliminate latency
- Not using batch mode for related writes — they should be atomic
- Storing large blobs in SQLite — use object storage instead
- Creating indexes after data grows large — define them upfront
- Not calling
db.sync()after writes when using embedded replicas with reads - Using SQLite-specific syntax that breaks portability if you might switch databases
Install this skill directly: skilldb add database-services-skills
Related Skills
Cassandra
Build with Apache Cassandra for high-availability distributed data. Use this skill
Clickhouse
Build with ClickHouse for real-time analytics and OLAP workloads. Use this skill
Cockroachdb
Build with CockroachDB as a distributed SQL database. Use this skill when the
Convex
Build with Convex as a reactive backend. Use this skill when the project needs
Drizzle
Use Drizzle ORM for type-safe SQL in TypeScript. Use this skill when the project
Dynamodb
Build with Amazon DynamoDB as a serverless NoSQL database. Use this skill when