Turso (SQLite on Edge) for Web App
Turso is a distributed database based on libSQL (SQLite fork), running on edge nodes worldwide. Queries execute on nearest node to user, latency < 10ms for read-heavy apps. Embedded replicas run database copy directly in app memory.
Architecture
Turso uses primary + replicas model:
- Primary — main database, all writes go here
- Replicas — read-only copies on edge (Frankfurt, Singapore, São Paulo, etc)
- Embedded Replica — local SQLite copy in Cloudflare Worker/Node.js process memory
For global read-heavy apps (<5% writes), embedded replica gives ~0ms read latency — query never leaves runtime.
Setup
# CLI
turso auth login
turso db create myapp --location ams # primary in Amsterdam
# Additional replicas
turso db replicate myapp --location sin # Singapore
turso db replicate myapp --location gru # São Paulo
# Get URL and token
turso db show myapp --url
turso db tokens create myapp
// .env
TURSO_DATABASE_URL="libsql://myapp-org.turso.io"
TURSO_AUTH_TOKEN="eyJhbGciOiJFZERTQSJ9..."
libSQL Client
import { createClient } from '@libsql/client';
const db = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});
// Queries — async/await
const result = await db.execute('SELECT * FROM articles WHERE published = 1 LIMIT 10');
console.log(result.rows);
// Parameterized queries
const post = await db.execute({
sql: 'SELECT * FROM articles WHERE slug = ?',
args: [slug],
});
// Transactions
const tx = await db.transaction('write');
await tx.execute('INSERT INTO articles (title, body) VALUES (?, ?)', ['Hello', 'World']);
await tx.execute('INSERT INTO tags (article_id, name) VALUES (?, ?)', [1, 'news']);
await tx.commit();
Embedded Replica for Zero-Latency Reads
import { createClient } from '@libsql/client';
import { resolve } from 'path';
const db = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
syncUrl: process.env.TURSO_DATABASE_URL!,
syncInterval: 60, // seconds — how often to pull changes
});
// First call — syncs replica
await db.sync();
// All SELECT now from local SQLite — 0ms
const posts = await db.execute('SELECT id, title FROM articles');
Embedded replica especially efficient for SSR on Cloudflare Workers: page renders without network requests to DB.
Drizzle ORM + Turso
npm install drizzle-orm @libsql/client
npm install -D drizzle-kit
// db/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
export const articles = sqliteTable('articles', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
slug: text('slug').notNull().unique(),
body: text('body'),
publishedAt: integer('published_at', { mode: 'timestamp' }),
});
// db/client.ts
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
const client = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});
export const db = drizzle(client);
// Queries
const posts = await db.select().from(articles)
.where(isNotNull(articles.publishedAt))
.orderBy(desc(articles.publishedAt))
.limit(10);
Multi-tenancy with Turso
Turso supports per-tenant databases:
// Create DB per tenant via API
async function provisionTenantDB(tenantSlug: string) {
const response = await fetch(`https://api.turso.tech/v1/organizations/${ORG}/databases`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TURSO_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: `tenant-${tenantSlug}`,
group: 'default',
}),
});
const { database } = await response.json();
// Create access token for tenant
const tokenRes = await fetch(
`https://api.turso.tech/v1/organizations/${ORG}/databases/${database.Name}/auth/tokens`,
{ method: 'POST', headers: { Authorization: `Bearer ${TURSO_API_TOKEN}` } }
);
const { jwt } = await tokenRes.json();
return { url: `libsql://${database.Name}-${ORG}.turso.io`, token: jwt };
}
Up to 10,000 databases on one account — viable model for micro-SaaS.
When to Choose Turso
Suitable:
- Read-heavy global apps (< 5% writes)
- Cloudflare Workers / Deno Deploy (no TCP, need HTTP)
- Micro-SaaS with database-per-tenant on SQLite
- Apps with simple schema without complex JOINs
Not suitable:
- Write-heavy loads (all writes go to primary — no sharding)
- Complex analytical queries (SQLite not ClickHouse)
- PostgreSQL-specific requirements (RLS, advanced types, PostGIS)
- ACID transactions on multiple tables with high concurrency
Timeline
Turso setup, libSQL client + Drizzle ORM, embedded replica for Cloudflare Workers, CI migrations: 1–2 days.







