Turso and libSQL: SQLite at the Edge
Turso and libSQL: SQLite at the Edge

SQLite is the most deployed database in the world. It is embedded in every smartphone, every browser, and countless server applications. But it has one fundamental limitation for web applications: it runs on a single machine. If your application runs across multiple servers or edge locations, you cannot share a SQLite file between them.
Turso solves this by building on libSQL, an open-source fork of SQLite that adds network access, replication, and multi-tenancy. The result is a database that gives you SQLite's simplicity and performance with the distribution capabilities of a client-server database. Your data lives close to your users, reads are local, and writes propagate through a replication protocol.
What Is libSQL?
libSQL is a fork of SQLite maintained by the Turso team. It is fully compatible with SQLite -- any SQLite database file works with libSQL, and any SQLite client library can connect to it. But libSQL adds capabilities that SQLite's project governance does not allow upstream:
- HTTP access: Query a libSQL database over HTTP, not just through the C API
- Replication: Built-in primary/replica replication for distributing data
- ALTER TABLE extensions: More flexible schema changes than stock SQLite
- Random ROWID: Avoid sequential ID guessing for security-sensitive applications
- WebAssembly user-defined functions: Extend the database with custom functions written in any language that compiles to Wasm
- Vector search: Native vector similarity search for AI/embedding workloads
Turso: The Managed Platform
Turso is the managed platform for libSQL. It handles provisioning, replication, backups, and access control. You interact with it through the CLI or dashboard and connect from your application via an HTTP-based protocol.
Getting Started
# Install the Turso CLI
curl -sSfL https://get.tur.so/install.sh | bash
# Authenticate
turso auth login
# Create a database
turso db create my-app-db
# Specify a primary location (closest to your write workload)
turso db create my-app-db --location sea # Seattle
Working with the CLI
# List databases
turso db list
# Open an interactive shell
turso db shell my-app-db
# Run a query directly
turso db shell my-app-db "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)"
turso db shell my-app-db "INSERT INTO users (name, email) VALUES ('Alice', '[email protected]')"
turso db shell my-app-db "SELECT * FROM users"
# Get the connection URL and auth token
turso db show my-app-db --url
turso db tokens create my-app-db
Adding Edge Replicas
Replicas bring read copies of your database to other regions:
# Add replicas in other locations
turso db replicate my-app-db lhr # London
turso db replicate my-app-db nrt # Tokyo
turso db replicate my-app-db gru # Sao Paulo
# List locations
turso db show my-app-db --locations
Reads are served from the nearest replica. Writes are forwarded to the primary and then replicated outward. This gives you single-digit millisecond read latency globally while maintaining consistency.
TypeScript SDK
The @libsql/client package is the official TypeScript/JavaScript SDK. It works in Node.js, Bun, Deno, and edge runtimes like Cloudflare Workers.
Installation
# npm
npm install @libsql/client
# Bun
bun add @libsql/client
Connecting
import { createClient } from "@libsql/client";
const db = createClient({
url: "libsql://my-app-db-username.turso.io",
authToken: process.env.TURSO_AUTH_TOKEN,
});
Queries
// Simple query
const result = await db.execute("SELECT * FROM users WHERE active = 1");
console.log(result.rows);
// [{ id: 1, name: "Alice", email: "[email protected]" }, ...]
// Parameterized query (prevents SQL injection)
const user = await db.execute({
sql: "SELECT * FROM users WHERE id = ?",
args: [userId],
});
// Named parameters
const users = await db.execute({
sql: "SELECT * FROM users WHERE name = :name AND role = :role",
args: { name: "Alice", role: "admin" },
});
// Insert and get the last inserted ID
const insert = await db.execute({
sql: "INSERT INTO users (name, email) VALUES (?, ?)",
args: ["Bob", "[email protected]"],
});
console.log(insert.lastInsertRowid); // 2n (BigInt)
Transactions
const tx = await db.transaction("write");
try {
await tx.execute({
sql: "INSERT INTO orders (user_id, total) VALUES (?, ?)",
args: [userId, 99.99],
});
await tx.execute({
sql: "UPDATE users SET order_count = order_count + 1 WHERE id = ?",
args: [userId],
});
await tx.commit();
} catch (e) {
await tx.rollback();
throw e;
}
Batch Queries
Send multiple statements in a single HTTP round-trip:
const results = await db.batch([
{ sql: "INSERT INTO logs (message) VALUES (?)", args: ["event_a"] },
{ sql: "INSERT INTO logs (message) VALUES (?)", args: ["event_b"] },
{ sql: "SELECT count(*) as total FROM logs", args: [] },
]);
console.log(results[2].rows[0].total); // Total log count
Embedded Replicas
The most powerful feature of Turso is embedded replicas. Instead of connecting to a remote database over the network, you can embed a local SQLite replica directly in your application. Reads hit the local file. Writes are forwarded to the primary and then synced back.
import { createClient } from "@libsql/client";
const db = createClient({
url: "libsql://my-app-db-username.turso.io",
authToken: process.env.TURSO_AUTH_TOKEN,
syncUrl: "libsql://my-app-db-username.turso.io",
syncInterval: 60, // Sync every 60 seconds
});
// Force a sync
await db.sync();
// Reads are now local (microseconds, not milliseconds)
const result = await db.execute("SELECT * FROM products WHERE category = 'tools'");
Embedded replicas give you:
- Local read speed: Reads are served from an on-disk SQLite file, so latency is measured in microseconds
- Offline capability: Your application can continue reading even if the network is down
- Reduced costs: Fewer network round-trips mean lower bandwidth costs
This pattern is ideal for read-heavy applications, edge deployments, and mobile backends.
Multi-Tenant Patterns
Turso supports a database-per-tenant architecture where each customer gets their own isolated database. This is SQLite's natural strength -- each database is a separate file with zero overhead when idle.
# Create a database per tenant
turso db create tenant-acme
turso db create tenant-globex
turso db create tenant-initech
In your application, route requests to the appropriate database:
function getDbForTenant(tenantId: string) {
return createClient({
url: `libsql://tenant-${tenantId}-username.turso.io`,
authToken: process.env.TURSO_AUTH_TOKEN,
});
}
// Each tenant has completely isolated data
const acmeDb = getDbForTenant("acme");
const globexDb = getDbForTenant("globex");
Turso supports up to 10,000 databases on the free tier and millions on paid plans. Each idle database consumes zero compute resources.
Turso vs. PlanetScale vs. Neon
| Feature | Turso (libSQL) | PlanetScale (MySQL) | Neon (PostgreSQL) |
|---|---|---|---|
| Base engine | SQLite | MySQL | PostgreSQL |
| Edge replicas | Built-in | Limited | Read replicas |
| Embedded replicas | Yes | No | No |
| Multi-tenant | DB per tenant | Schema per tenant | DB per tenant |
| Cold start | None (SQLite) | Seconds | Seconds |
| Branching | Yes | Yes | Yes |
| Free tier | 9 GB, 500 DBs | Deprecated | 0.5 GB |
| Best for | Edge apps, multi-tenant | MySQL-native apps | Postgres-native apps |
Choose Turso when: You want SQLite simplicity, need edge distribution, or are building a multi-tenant SaaS where database-per-tenant isolation makes sense.
Choose Neon when: Your application is built on PostgreSQL and you need Postgres-specific features (advanced indexing, extensions, JSON operators, full-text search).
Choose PlanetScale when: Your application is MySQL-native and you need MySQL compatibility.
Practical Tips
- Use parameterized queries always: The
@libsql/clientmakes this easy with theargsparameter. Never concatenate user input into SQL strings. - Set up embedded replicas for read-heavy workloads: If your read-to-write ratio is 10:1 or higher, embedded replicas can cut your P99 latency dramatically.
- Use database branching for development:
turso db create my-app-db-dev --from-db my-app-dbcreates a branch with a copy of your production schema and data for testing. - Monitor replication lag: In write-heavy applications, replicas may lag behind the primary. Design your application to tolerate eventual consistency on reads, or use the primary directly for read-after-write scenarios.
- Use batch queries: Every HTTP round-trip adds latency. Batch multiple operations into a single request whenever possible.
- Keep databases small: SQLite excels with databases under 10 GB. For larger datasets, consider sharding across multiple databases using your multi-tenant architecture.
Turso and libSQL make a compelling case for SQLite as a distributed database. The combination of SQLite's simplicity, edge replication, and embedded replicas creates an architecture that is both simpler and faster than traditional client-server databases for many workloads. If your application can model its data in SQLite, Turso removes the scaling barriers that previously made SQLite impractical for production web applications.