Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds
Torna alle collezioni
Guide33 min read

Turso

Turso is a distributed SQLite database at the edge with global replication, embedded replicas, and ultra-low latencies. Built on libSQL - an open fork of SQLite.

Turso - Kompletny Przewodnik po Edge SQLite Database

Czym jest Turso?

Turso to rozproszona SQLite database zaprojektowana dla nowoczesnych aplikacji edge. Wykorzystuje libSQL - otwarty fork SQLite rozwijany przez zespół Turso - który dodaje funkcje takie jak replikacja, HTTP/WebSocket dostęp i rozszerzenia, zachowując pełną kompatybilność z SQLite.

Główna innowacja Turso to możliwość umieszczenia replik bazy danych blisko użytkowników na całym świecie (edge locations), co drastycznie redukuje latencje. Dodatkowo Turso oferuje embedded replicas - lokalną kopię bazy w aplikacji, która synchronizuje się z główną bazą, zapewniając mikrosekundowe czasy odczytu.

Dlaczego Turso?

Kluczowe zalety Turso

  1. Edge locations - Repliki na całym świecie, blisko użytkowników
  2. Embedded replicas - Lokalna kopia bazy w aplikacji
  3. libSQL - Otwarty fork SQLite z dodatkowymi funkcjami
  4. Ultra-niskie latencje - Mikrosekundy dla lokalnych odczytów
  5. SQLite compatibility - Pełna kompatybilność z SQLite
  6. Serverless - Automatyczne skalowanie, brak zarządzania
  7. Cost-effective - Znacznie tańszy niż PostgreSQL/MySQL
  8. Simple - Jeden plik = cała baza danych

Turso vs Inne Bazy Danych

CechaTursoPlanetScaleNeonD1 (Cloudflare)
EngineSQLite/libSQLMySQL/VitessPostgreSQLSQLite
Edge locations30+10+10+Cloudflare PoPs
Embedded replicasTakNieNieNie
Local latency~0.5ms~50ms~50ms~5ms
Free tier9GB5GB3GB5GB
BranchingTakTakTakNie
Open sourcelibSQL (tak)NieNieNie

Turso vs SQLite

CechaTursoSQLite
ReplikacjaAutomatycznaBrak
Edge deploymentTakNie
HTTP accessTakNie
WebsocketsTakNie
SkalowanieAutomatyczneManualne
BackupyAutomatyczneManualne
Multi-regionTakNie

Kiedy wybrać Turso?

  • Edge applications - Dane blisko użytkowników
  • Read-heavy workloads - Większość operacji to odczyty
  • Low latency critical - Mikrosekundy mają znaczenie
  • Simple schema - Relacyjne dane bez złożonych JOINów
  • Cost-conscious - Tańsza alternatywa dla PostgreSQL

Instalacja i Konfiguracja

Turso CLI

Code
Bash
# macOS / Linux
curl -sSfL https://get.tur.so/install.sh | bash

# macOS (Homebrew)
brew install tursodatabase/tap/turso

# Weryfikacja
turso --version

# Windows (WSL wymagany)
curl -sSfL https://get.tur.so/install.sh | bash

Logowanie i tworzenie bazy

Code
Bash
# Zaloguj się (otworzy przeglądarkę)
turso auth login

# Lub z tokenem
turso auth login --headless

# Stwórz nową bazę danych
turso db create my-database

# Z konkretną lokalizacją
turso db create my-database --location waw

# Lista baz
turso db list

# Szczegóły bazy
turso db show my-database

Dostępne lokalizacje (30+)

Code
Bash
# Lista wszystkich lokalizacji
turso db locations

# Popularne lokalizacje:
# waw - Warsaw, Poland
# fra - Frankfurt, Germany
# lhr - London, UK
# ams - Amsterdam, Netherlands
# cdg - Paris, France
# iad - N. Virginia, USA
# sfo - San Francisco, USA
# sin - Singapore
# nrt - Tokyo, Japan
# syd - Sydney, Australia
# gru - São Paulo, Brazil

Tworzenie tokenu

Code
Bash
# Token z pełnymi uprawnieniami
turso db tokens create my-database

# Token read-only
turso db tokens create my-database --read-only

# Token z expiration
turso db tokens create my-database --expiration 7d

# Revoke token
turso db tokens revoke my-database <token-name>

Podstawowe Użycie

Turso CLI Shell

Code
Bash
# Połącz się z bazą (interaktywny shell)
turso db shell my-database

# Wykonaj SQL
turso> CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  name TEXT,
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
);

turso> INSERT INTO users (email, name) VALUES ('user@example.com', 'John');

turso> SELECT * FROM users;

# Wyjście
turso> .quit

TypeScript/JavaScript Client

Code
Bash
npm install @libsql/client
TSdb/client.ts
TypeScript
// db/client.ts
import { createClient } from '@libsql/client'

export const db = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!
})

// Lub dla lokalnego developmentu
// export const db = createClient({
//   url: 'file:local.db'
// })
Code
TypeScript
// Podstawowe operacje
import { db } from './db/client'

// Query
const result = await db.execute('SELECT * FROM users')
console.log(result.rows)

// Z parametrami (named)
const user = await db.execute({
  sql: 'SELECT * FROM users WHERE id = :id',
  args: { id: 1 }
})

// Z parametrami (positional)
const users = await db.execute({
  sql: 'SELECT * FROM users WHERE email = ?',
  args: ['user@example.com']
})

// Insert
await db.execute({
  sql: 'INSERT INTO users (email, name) VALUES (?, ?)',
  args: ['new@example.com', 'Jane']
})

// Update
await db.execute({
  sql: 'UPDATE users SET name = ? WHERE id = ?',
  args: ['Updated Name', 1]
})

// Delete
await db.execute({
  sql: 'DELETE FROM users WHERE id = ?',
  args: [1]
})

Transactions

Code
TypeScript
// Pojedyncza transakcja
const result = await db.transaction(async (tx) => {
  await tx.execute({
    sql: 'INSERT INTO accounts (user_id, balance) VALUES (?, ?)',
    args: [userId, 1000]
  })

  await tx.execute({
    sql: 'INSERT INTO transactions (account_id, amount) VALUES (?, ?)',
    args: [accountId, -100]
  })

  await tx.execute({
    sql: 'UPDATE accounts SET balance = balance - ? WHERE id = ?',
    args: [100, accountId]
  })

  return { success: true }
})

Batch Queries

Code
TypeScript
// Wykonaj wiele queries w jednym roundtrip
const results = await db.batch([
  {
    sql: 'INSERT INTO users (email, name) VALUES (?, ?)',
    args: ['user1@example.com', 'User 1']
  },
  {
    sql: 'INSERT INTO users (email, name) VALUES (?, ?)',
    args: ['user2@example.com', 'User 2']
  },
  {
    sql: 'SELECT * FROM users'
  }
])

// results[0] - pierwszy INSERT
// results[1] - drugi INSERT
// results[2] - SELECT

Edge Replicas

Dodawanie replik

Code
Bash
# Dodaj replikę w konkretnej lokalizacji
turso db replicate my-database waw
turso db replicate my-database fra
turso db replicate my-database sin

# Lista replik
turso db show my-database

# Usuń replikę
turso db replicate my-database waw --remove

Automatyczne routing

Code
TypeScript
// Klient automatycznie łączy się z najbliższą repliką
const db = createClient({
  url: 'libsql://my-database-username.turso.io',
  authToken: process.env.TURSO_AUTH_TOKEN
})

// Reads -> najbliższa replika
// Writes -> primary (propagowane do replik)

Manual routing

Code
TypeScript
// Połączenie do konkretnej lokalizacji
const db = createClient({
  url: 'libsql://my-database-username.turso.io?location=waw',
  authToken: process.env.TURSO_AUTH_TOKEN
})

Embedded Replicas

Embedded replicas to game-changer - lokalna kopia bazy w aplikacji!

Konfiguracja

Code
TypeScript
import { createClient } from '@libsql/client'

const db = createClient({
  // Lokalna replika
  url: 'file:local-replica.db',

  // Synchronizacja z remote
  syncUrl: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,

  // Opcjonalnie: automatyczna synchronizacja
  syncInterval: 60  // sekundy
})

Manual Sync

Code
TypeScript
// Synchronizuj ręcznie
await db.sync()

// Po INSERT/UPDATE na remote
// Dane pojawią się lokalnie po sync

// Typowy flow:
// 1. Użytkownik czyta z embedded replica (~0.5ms)
// 2. Użytkownik zapisuje do remote
// 3. Aplikacja sync()
// 4. Nowe dane dostępne lokalnie

Use Cases dla Embedded Replicas

Code
TypeScript
// 1. Read-heavy aplikacje
// Większość odczytów z lokalnej repliki
const users = await db.execute('SELECT * FROM users LIMIT 100')
// ~0.5ms zamiast ~50ms!

// 2. Offline-first apps
// Dane dostępne bez internetu
const cachedData = await db.execute('SELECT * FROM cache')

// 3. Edge functions
// Lokalna replika w Vercel Edge Function
export const config = { runtime: 'edge' }

export default async function handler() {
  const data = await db.execute('SELECT * FROM products')
  return Response.json(data.rows)
}

Embedded Replicas w Next.js

TSlib/db.ts
TypeScript
// lib/db.ts
import { createClient } from '@libsql/client'

// Singleton pattern dla embedded replica
let db: ReturnType<typeof createClient> | null = null

export function getDb() {
  if (!db) {
    db = createClient({
      url: 'file:./local.db',
      syncUrl: process.env.TURSO_DATABASE_URL,
      authToken: process.env.TURSO_AUTH_TOKEN
    })
  }
  return db
}

// Sync na starcie aplikacji
export async function initDb() {
  const db = getDb()
  await db.sync()
}
TSapp/api/users/route.ts
TypeScript
// app/api/users/route.ts
import { getDb } from '@/lib/db'
import { NextResponse } from 'next/server'

export async function GET() {
  const db = getDb()

  // Super szybki odczyt z lokalnej repliki
  const result = await db.execute('SELECT * FROM users')

  return NextResponse.json(result.rows)
}

export async function POST(request: Request) {
  const db = getDb()
  const data = await request.json()

  // Zapis idzie do remote
  await db.execute({
    sql: 'INSERT INTO users (email, name) VALUES (?, ?)',
    args: [data.email, data.name]
  })

  // Sync dla natychmiastowej dostępności lokalnie
  await db.sync()

  return NextResponse.json({ success: true })
}

Drizzle ORM

Setup

Code
Bash
npm install drizzle-orm @libsql/client
npm install -D drizzle-kit
TSdb/schema.ts
TypeScript
// db/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'

export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  email: text('email').notNull().unique(),
  name: text('name'),
  createdAt: text('created_at').default('CURRENT_TIMESTAMP')
})

export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  title: text('title').notNull(),
  content: text('content'),
  authorId: integer('author_id').notNull().references(() => users.id),
  published: integer('published', { mode: 'boolean' }).default(false),
  createdAt: text('created_at').default('CURRENT_TIMESTAMP')
})
TSdb/index.ts
TypeScript
// db/index.ts
import { drizzle } from 'drizzle-orm/libsql'
import { createClient } from '@libsql/client'
import * as schema from './schema'

const client = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!
})

export const db = drizzle(client, { schema })
TSdrizzle.config.ts
TypeScript
// drizzle.config.ts
import type { Config } from 'drizzle-kit'

export default {
  schema: './db/schema.ts',
  out: './drizzle',
  driver: 'turso',
  dbCredentials: {
    url: process.env.TURSO_DATABASE_URL!,
    authToken: process.env.TURSO_AUTH_TOKEN!
  }
} satisfies Config

Migracje

Code
Bash
# Generuj migracje
npx drizzle-kit generate:sqlite

# Aplikuj migracje
npx drizzle-kit push:sqlite

# Studio (GUI)
npx drizzle-kit studio

Queries z Drizzle

Code
TypeScript
import { db } from '@/db'
import { users, posts } from '@/db/schema'
import { eq, and, desc, like } from 'drizzle-orm'

// Select all
const allUsers = await db.select().from(users)

// Select with where
const user = await db
  .select()
  .from(users)
  .where(eq(users.id, 1))

// Select specific columns
const emails = await db
  .select({ email: users.email })
  .from(users)

// Join
const postsWithAuthors = await db
  .select({
    post: posts,
    author: users
  })
  .from(posts)
  .innerJoin(users, eq(posts.authorId, users.id))
  .where(eq(posts.published, true))
  .orderBy(desc(posts.createdAt))

// Insert
const [newUser] = await db
  .insert(users)
  .values({
    email: 'new@example.com',
    name: 'New User'
  })
  .returning()

// Update
await db
  .update(users)
  .set({ name: 'Updated' })
  .where(eq(users.id, 1))

// Delete
await db
  .delete(users)
  .where(eq(users.id, 1))

// Search
const searchResults = await db
  .select()
  .from(users)
  .where(like(users.name, '%john%'))

Prisma

Setup

Code
Bash
npm install prisma @prisma/client
npm install @prisma/adapter-libsql @libsql/client
npx prisma init
prisma/schema.prisma
Prisma
// prisma/schema.prisma
generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
}
TSlib/prisma.ts
TypeScript
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
import { PrismaLibSQL } from '@prisma/adapter-libsql'
import { createClient } from '@libsql/client'

const libsql = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!
})

const adapter = new PrismaLibSQL(libsql)

export const prisma = new PrismaClient({ adapter })
Code
Bash
# Push schema do Turso
npx prisma db push

# Generuj client
npx prisma generate
Code
TypeScript
// Użycie
import { prisma } from '@/lib/prisma'

// Create
const user = await prisma.user.create({
  data: {
    email: 'user@example.com',
    name: 'John'
  }
})

// Read with relations
const posts = await prisma.post.findMany({
  where: { published: true },
  include: { author: true }
})

// Update
await prisma.user.update({
  where: { id: 1 },
  data: { name: 'Updated' }
})

// Delete
await prisma.post.delete({
  where: { id: 1 }
})

Database Groups

Groups pozwalają zarządzać wieloma bazami razem:

Code
Bash
# Stwórz grupę
turso group create my-group --location waw

# Dodaj bazę do grupy
turso db create my-db --group my-group

# Wszystkie bazy w grupie dziedziczą lokalizacje
turso group locations add my-group fra

# Lista grup
turso group list

# Tokeny na poziomie grupy
turso group tokens create my-group

Multi-tenant Architecture

Code
TypeScript
// Każdy tenant ma własną bazę w grupie
async function createTenantDb(tenantId: string) {
  // Użyj Turso Platform API
  const response = await fetch(
    `https://api.turso.tech/v1/organizations/${orgId}/databases`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${TURSO_API_TOKEN}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: `tenant-${tenantId}`,
        group: 'tenants-group'
      })
    }
  )

  return response.json()
}

// Routing do właściwej bazy
function getTenantDb(tenantId: string) {
  return createClient({
    url: `libsql://tenant-${tenantId}-${org}.turso.io`,
    authToken: process.env.TURSO_AUTH_TOKEN
  })
}

Schema Management

Turso CLI Schema

Code
Bash
# Pokaż schemat
turso db shell my-database ".schema"

# Export schema
turso db shell my-database ".schema" > schema.sql

# Import schema
turso db shell my-database < schema.sql

Migracje z Drizzle

Code
Bash
# Struktura
migrations/
├── 0000_init.sql
├── 0001_add_posts.sql
└── 0002_add_comments.sql
TSmigrate.ts
TypeScript
// migrate.ts
import { migrate } from 'drizzle-orm/libsql/migrator'
import { db } from './db'

async function main() {
  console.log('Running migrations...')
  await migrate(db, { migrationsFolder: './drizzle' })
  console.log('Migrations complete!')
}

main()

Schema Dump i Restore

Code
Bash
# Dump całej bazy
turso db shell my-database ".dump" > backup.sql

# Restore
turso db create my-database-restored
turso db shell my-database-restored < backup.sql

Integracje

Next.js App Router

TSapp/api/users/route.ts
TypeScript
// app/api/users/route.ts
import { db } from '@/lib/db'
import { users } from '@/lib/db/schema'
import { NextResponse } from 'next/server'

export async function GET() {
  const allUsers = await db.select().from(users)
  return NextResponse.json(allUsers)
}

export async function POST(request: Request) {
  const data = await request.json()

  const [user] = await db
    .insert(users)
    .values(data)
    .returning()

  return NextResponse.json(user, { status: 201 })
}

Vercel Edge Functions

TSapp/api/edge/route.ts
TypeScript
// app/api/edge/route.ts
import { createClient } from '@libsql/client/web'

export const runtime = 'edge'

const db = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!
})

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const id = searchParams.get('id')

  const result = await db.execute({
    sql: 'SELECT * FROM users WHERE id = ?',
    args: [id]
  })

  return Response.json(result.rows[0])
}

SvelteKit

TSsrc/lib/db.ts
TypeScript
// src/lib/db.ts
import { createClient } from '@libsql/client'
import { TURSO_DATABASE_URL, TURSO_AUTH_TOKEN } from '$env/static/private'

export const db = createClient({
  url: TURSO_DATABASE_URL,
  authToken: TURSO_AUTH_TOKEN
})
TSsrc/routes/api/users/+server.ts
TypeScript
// src/routes/api/users/+server.ts
import { db } from '$lib/db'
import { json } from '@sveltejs/kit'
import type { RequestHandler } from './$types'

export const GET: RequestHandler = async () => {
  const result = await db.execute('SELECT * FROM users')
  return json(result.rows)
}

Remix

TSapp/utils/db.server.ts
TypeScript
// app/utils/db.server.ts
import { createClient } from '@libsql/client'

export const db = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!
})
TSapp/routes/users.tsx
TypeScript
// app/routes/users.tsx
import { json } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { db } from '~/utils/db.server'

export async function loader() {
  const result = await db.execute('SELECT * FROM users')
  return json({ users: result.rows })
}

export default function Users() {
  const { users } = useLoaderData<typeof loader>()

  return (
    <ul>
      {users.map((user: any) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

GitHub Actions CI/CD

.github/workflows/migrate.yml
YAML
# .github/workflows/migrate.yml
name: Database Migration

on:
  push:
    branches: [main]
    paths: ['drizzle/**']

jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci

      - name: Run migrations
        run: npx drizzle-kit push:sqlite
        env:
          TURSO_DATABASE_URL: ${{ secrets.TURSO_DATABASE_URL }}
          TURSO_AUTH_TOKEN: ${{ secrets.TURSO_AUTH_TOKEN }}

Performance Tips

Indeksy

Code
SQL
-- Twórz indeksy dla często używanych kolumn
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_author ON posts(author_id);
CREATE INDEX idx_posts_created ON posts(created_at DESC);

-- Composite index
CREATE INDEX idx_posts_author_published
ON posts(author_id, published);

Query Optimization

Code
TypeScript
// ❌ Pobierz wszystko
const users = await db.execute('SELECT * FROM users')

// ✅ Pobierz tylko potrzebne kolumny
const users = await db.execute('SELECT id, name FROM users')

// ❌ Brak limitu
const posts = await db.execute('SELECT * FROM posts ORDER BY created_at DESC')

// ✅ Z limitem
const posts = await db.execute({
  sql: 'SELECT * FROM posts ORDER BY created_at DESC LIMIT ?',
  args: [20]
})

// ✅ Pagination
const posts = await db.execute({
  sql: 'SELECT * FROM posts ORDER BY id LIMIT ? OFFSET ?',
  args: [20, page * 20]
})

Connection Reuse

Code
TypeScript
// ❌ Nowy client przy każdym request
export async function handler() {
  const db = createClient({ ... })
  // ...
}

// ✅ Singleton pattern
let db: ReturnType<typeof createClient> | null = null

export function getDb() {
  if (!db) {
    db = createClient({
      url: process.env.TURSO_DATABASE_URL!,
      authToken: process.env.TURSO_AUTH_TOKEN!
    })
  }
  return db
}

Batch vs Multiple Queries

Code
TypeScript
// ❌ Wiele roundtrips
await db.execute({ sql: 'INSERT INTO users ...', args: [...] })
await db.execute({ sql: 'INSERT INTO users ...', args: [...] })
await db.execute({ sql: 'INSERT INTO users ...', args: [...] })

// ✅ Jeden roundtrip
await db.batch([
  { sql: 'INSERT INTO users ...', args: [...] },
  { sql: 'INSERT INTO users ...', args: [...] },
  { sql: 'INSERT INTO users ...', args: [...] }
])

Cennik

Starter (Free)

  • Storage: 9GB total
  • Databases: 500
  • Row reads: 1 miliard/miesiąc
  • Row writes: 25 milionów/miesiąc
  • Locations: 3 repliki
  • Groups: 3

Scaler ($29/miesiąc)

  • Storage: 24GB (+ $0.75/GB)
  • Databases: Unlimited
  • Row reads: Unlimited
  • Row writes: 100 milionów (+ $1/milion)
  • Locations: 6 replik
  • Groups: Unlimited
  • Point-in-time recovery

Enterprise (Custom)

  • Dedicated support
  • SLA
  • Custom limits
  • SSO/SAML
  • Audit logs

Best Practices

Development Workflow

Code
Bash
# 1. Lokalna baza do developmentu
turso db create my-app-dev

# 2. Staging
turso db create my-app-staging

# 3. Production
turso db create my-app-prod --location waw
turso db replicate my-app-prod fra
turso db replicate my-app-prod iad

Environment Variables

.env.local
ENV
# .env.local (development)
TURSO_DATABASE_URL=libsql://my-app-dev-username.turso.io
TURSO_AUTH_TOKEN=eyJhbGci...

# .env.production
TURSO_DATABASE_URL=libsql://my-app-prod-username.turso.io
TURSO_AUTH_TOKEN=eyJhbGci...

Error Handling

Code
TypeScript
import { db } from '@/lib/db'

async function getUser(id: number) {
  try {
    const result = await db.execute({
      sql: 'SELECT * FROM users WHERE id = ?',
      args: [id]
    })

    if (result.rows.length === 0) {
      return null
    }

    return result.rows[0]
  } catch (error) {
    if (error instanceof Error) {
      console.error('Database error:', error.message)

      // Retry logic dla network errors
      if (error.message.includes('network')) {
        // Retry...
      }
    }
    throw error
  }
}

Type Safety

TStypes/db.ts
TypeScript
// types/db.ts
interface User {
  id: number
  email: string
  name: string | null
  createdAt: string
}

// Typowany query
async function getUsers(): Promise<User[]> {
  const result = await db.execute('SELECT * FROM users')
  return result.rows as User[]
}

// Lub z Drizzle (pełna type safety)
import { db } from '@/lib/db'
import { users } from '@/lib/db/schema'

const allUsers = await db.select().from(users)
// allUsers jest automatycznie typowany!

FAQ - Najczęściej Zadawane Pytania

Kiedy używać embedded replicas?

Gdy masz read-heavy workload i zależy Ci na ultra-niskich latencjach. Idealne dla e-commerce (produkty), CMS (artykuły), analytics dashboards.

Czy Turso wspiera foreign keys?

Tak! W przeciwieństwie do PlanetScale, Turso/libSQL w pełni wspiera SQLite foreign keys.

Jak działa synchronizacja embedded replicas?

Embedded replica synchronizuje się z primary przy wywołaniu db.sync(). Możesz też ustawić syncInterval dla automatycznej synchronizacji.

Czy mogę używać Turso z istniejącą bazą SQLite?

Tak! Możesz zaimportować istniejącą bazę SQLite do Turso przez CLI lub API.

Jaka jest różnica między Turso a D1 (Cloudflare)?

D1 działa tylko na Cloudflare Workers. Turso jest niezależny od platformy i oferuje embedded replicas oraz więcej edge locations.

Czy Turso wspiera full-text search?

Tak, przez SQLite FTS5 extension. libSQL wspiera wszystkie standardowe rozszerzenia SQLite.

Code
SQL
-- FTS5 przykład
CREATE VIRTUAL TABLE posts_fts USING fts5(title, content);
INSERT INTO posts_fts SELECT title, content FROM posts;
SELECT * FROM posts_fts WHERE posts_fts MATCH 'search query';

Podsumowanie

Turso to idealna baza danych dla nowoczesnych edge aplikacji:

  • Edge locations - dane blisko użytkowników
  • Embedded replicas - mikrosekundowe latencje
  • libSQL - otwarty SQLite z supermocami
  • Serverless - zero zarządzania infrastrukturą
  • Cost-effective - znacznie tańszy niż alternatywy

Świetny wybór dla aplikacji wymagających niskich latencji, read-heavy workloads i prostej architektury bazodanowej.


Turso - a complete guide to edge SQLite database

What is Turso?

Turso is a distributed SQLite database designed for modern edge applications. It uses libSQL - an open fork of SQLite developed by the Turso team - which adds features like replication, HTTP/WebSocket access, and extensions while maintaining full SQLite compatibility.

The main innovation behind Turso is the ability to place database replicas close to users around the world (edge locations), which drastically reduces latencies. On top of that, Turso offers embedded replicas - a local copy of the database inside your application that syncs with the primary database, delivering microsecond read times.

Why Turso?

Key advantages of Turso

  1. Edge locations - Replicas around the world, close to users
  2. Embedded replicas - Local database copy inside your app
  3. libSQL - Open fork of SQLite with additional features
  4. Ultra-low latencies - Microseconds for local reads
  5. SQLite compatibility - Full compatibility with SQLite
  6. Serverless - Automatic scaling, zero management
  7. Cost-effective - Significantly cheaper than PostgreSQL/MySQL
  8. Simple - One file = entire database

Turso vs other databases

FeatureTursoPlanetScaleNeonD1 (Cloudflare)
EngineSQLite/libSQLMySQL/VitessPostgreSQLSQLite
Edge locations30+10+10+Cloudflare PoPs
Embedded replicasYesNoNoNo
Local latency~0.5ms~50ms~50ms~5ms
Free tier9GB5GB3GB5GB
BranchingYesYesYesNo
Open sourcelibSQL (yes)NoNoNo

Turso vs SQLite

FeatureTursoSQLite
ReplicationAutomaticNone
Edge deploymentYesNo
HTTP accessYesNo
WebsocketsYesNo
ScalingAutomaticManual
BackupsAutomaticManual
Multi-regionYesNo

When to choose Turso?

  • Edge applications - Data close to users
  • Read-heavy workloads - Most operations are reads
  • Low latency critical - Microseconds matter
  • Simple schema - Relational data without complex JOINs
  • Cost-conscious - Cheaper alternative to PostgreSQL

Installation and configuration

Turso CLI

Code
Bash
# macOS / Linux
curl -sSfL https://get.tur.so/install.sh | bash

# macOS (Homebrew)
brew install tursodatabase/tap/turso

# Verify
turso --version

# Windows (WSL required)
curl -sSfL https://get.tur.so/install.sh | bash

Login and creating a database

Code
Bash
# Log in (opens browser)
turso auth login

# Or with a token
turso auth login --headless

# Create a new database
turso db create my-database

# With a specific location
turso db create my-database --location waw

# List databases
turso db list

# Database details
turso db show my-database

Available locations (30+)

Code
Bash
# List all locations
turso db locations

# Popular locations:
# waw - Warsaw, Poland
# fra - Frankfurt, Germany
# lhr - London, UK
# ams - Amsterdam, Netherlands
# cdg - Paris, France
# iad - N. Virginia, USA
# sfo - San Francisco, USA
# sin - Singapore
# nrt - Tokyo, Japan
# syd - Sydney, Australia
# gru - São Paulo, Brazil

Creating a token

Code
Bash
# Token with full permissions
turso db tokens create my-database

# Read-only token
turso db tokens create my-database --read-only

# Token with expiration
turso db tokens create my-database --expiration 7d

# Revoke token
turso db tokens revoke my-database <token-name>

Basic usage

Turso CLI Shell

Code
Bash
# Connect to the database (interactive shell)
turso db shell my-database

# Execute SQL
turso> CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  name TEXT,
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
);

turso> INSERT INTO users (email, name) VALUES ('user@example.com', 'John');

turso> SELECT * FROM users;

# Exit
turso> .quit

TypeScript/JavaScript client

Code
Bash
npm install @libsql/client
TSdb/client.ts
TypeScript
// db/client.ts
import { createClient } from '@libsql/client'

export const db = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!
})

// Or for local development
// export const db = createClient({
//   url: 'file:local.db'
// })
Code
TypeScript
// Basic operations
import { db } from './db/client'

// Query
const result = await db.execute('SELECT * FROM users')
console.log(result.rows)

// With parameters (named)
const user = await db.execute({
  sql: 'SELECT * FROM users WHERE id = :id',
  args: { id: 1 }
})

// With parameters (positional)
const users = await db.execute({
  sql: 'SELECT * FROM users WHERE email = ?',
  args: ['user@example.com']
})

// Insert
await db.execute({
  sql: 'INSERT INTO users (email, name) VALUES (?, ?)',
  args: ['new@example.com', 'Jane']
})

// Update
await db.execute({
  sql: 'UPDATE users SET name = ? WHERE id = ?',
  args: ['Updated Name', 1]
})

// Delete
await db.execute({
  sql: 'DELETE FROM users WHERE id = ?',
  args: [1]
})

Transactions

Code
TypeScript
// Single transaction
const result = await db.transaction(async (tx) => {
  await tx.execute({
    sql: 'INSERT INTO accounts (user_id, balance) VALUES (?, ?)',
    args: [userId, 1000]
  })

  await tx.execute({
    sql: 'INSERT INTO transactions (account_id, amount) VALUES (?, ?)',
    args: [accountId, -100]
  })

  await tx.execute({
    sql: 'UPDATE accounts SET balance = balance - ? WHERE id = ?',
    args: [100, accountId]
  })

  return { success: true }
})

Batch queries

Code
TypeScript
// Execute multiple queries in a single roundtrip
const results = await db.batch([
  {
    sql: 'INSERT INTO users (email, name) VALUES (?, ?)',
    args: ['user1@example.com', 'User 1']
  },
  {
    sql: 'INSERT INTO users (email, name) VALUES (?, ?)',
    args: ['user2@example.com', 'User 2']
  },
  {
    sql: 'SELECT * FROM users'
  }
])

// results[0] - first INSERT
// results[1] - second INSERT
// results[2] - SELECT

Edge replicas

Adding replicas

Code
Bash
# Add a replica in a specific location
turso db replicate my-database waw
turso db replicate my-database fra
turso db replicate my-database sin

# List replicas
turso db show my-database

# Remove a replica
turso db replicate my-database waw --remove

Automatic routing

Code
TypeScript
// The client automatically connects to the nearest replica
const db = createClient({
  url: 'libsql://my-database-username.turso.io',
  authToken: process.env.TURSO_AUTH_TOKEN
})

// Reads -> nearest replica
// Writes -> primary (propagated to replicas)

Manual routing

Code
TypeScript
// Connect to a specific location
const db = createClient({
  url: 'libsql://my-database-username.turso.io?location=waw',
  authToken: process.env.TURSO_AUTH_TOKEN
})

Embedded replicas

Embedded replicas are a game-changer - a local copy of the database right inside your app!

Configuration

Code
TypeScript
import { createClient } from '@libsql/client'

const db = createClient({
  // Local replica
  url: 'file:local-replica.db',

  // Sync with remote
  syncUrl: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,

  // Optional: automatic sync
  syncInterval: 60  // seconds
})

Manual sync

Code
TypeScript
// Sync manually
await db.sync()

// After INSERT/UPDATE on remote
// Data will appear locally after sync

// Typical flow:
// 1. User reads from embedded replica (~0.5ms)
// 2. User writes to remote
// 3. App calls sync()
// 4. New data available locally

Use cases for embedded replicas

Code
TypeScript
// 1. Read-heavy applications
// Most reads from the local replica
const users = await db.execute('SELECT * FROM users LIMIT 100')
// ~0.5ms instead of ~50ms!

// 2. Offline-first apps
// Data available without internet
const cachedData = await db.execute('SELECT * FROM cache')

// 3. Edge functions
// Local replica in a Vercel Edge Function
export const config = { runtime: 'edge' }

export default async function handler() {
  const data = await db.execute('SELECT * FROM products')
  return Response.json(data.rows)
}

Embedded replicas in Next.js

TSlib/db.ts
TypeScript
// lib/db.ts
import { createClient } from '@libsql/client'

// Singleton pattern for the embedded replica
let db: ReturnType<typeof createClient> | null = null

export function getDb() {
  if (!db) {
    db = createClient({
      url: 'file:./local.db',
      syncUrl: process.env.TURSO_DATABASE_URL,
      authToken: process.env.TURSO_AUTH_TOKEN
    })
  }
  return db
}

// Sync on application startup
export async function initDb() {
  const db = getDb()
  await db.sync()
}
TSapp/api/users/route.ts
TypeScript
// app/api/users/route.ts
import { getDb } from '@/lib/db'
import { NextResponse } from 'next/server'

export async function GET() {
  const db = getDb()

  // Super fast read from the local replica
  const result = await db.execute('SELECT * FROM users')

  return NextResponse.json(result.rows)
}

export async function POST(request: Request) {
  const db = getDb()
  const data = await request.json()

  // Write goes to remote
  await db.execute({
    sql: 'INSERT INTO users (email, name) VALUES (?, ?)',
    args: [data.email, data.name]
  })

  // Sync for immediate local availability
  await db.sync()

  return NextResponse.json({ success: true })
}

Drizzle ORM

Setup

Code
Bash
npm install drizzle-orm @libsql/client
npm install -D drizzle-kit
TSdb/schema.ts
TypeScript
// db/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'

export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  email: text('email').notNull().unique(),
  name: text('name'),
  createdAt: text('created_at').default('CURRENT_TIMESTAMP')
})

export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  title: text('title').notNull(),
  content: text('content'),
  authorId: integer('author_id').notNull().references(() => users.id),
  published: integer('published', { mode: 'boolean' }).default(false),
  createdAt: text('created_at').default('CURRENT_TIMESTAMP')
})
TSdb/index.ts
TypeScript
// db/index.ts
import { drizzle } from 'drizzle-orm/libsql'
import { createClient } from '@libsql/client'
import * as schema from './schema'

const client = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!
})

export const db = drizzle(client, { schema })
TSdrizzle.config.ts
TypeScript
// drizzle.config.ts
import type { Config } from 'drizzle-kit'

export default {
  schema: './db/schema.ts',
  out: './drizzle',
  driver: 'turso',
  dbCredentials: {
    url: process.env.TURSO_DATABASE_URL!,
    authToken: process.env.TURSO_AUTH_TOKEN!
  }
} satisfies Config

Migrations

Code
Bash
# Generate migrations
npx drizzle-kit generate:sqlite

# Apply migrations
npx drizzle-kit push:sqlite

# Studio (GUI)
npx drizzle-kit studio

Queries with Drizzle

Code
TypeScript
import { db } from '@/db'
import { users, posts } from '@/db/schema'
import { eq, and, desc, like } from 'drizzle-orm'

// Select all
const allUsers = await db.select().from(users)

// Select with where
const user = await db
  .select()
  .from(users)
  .where(eq(users.id, 1))

// Select specific columns
const emails = await db
  .select({ email: users.email })
  .from(users)

// Join
const postsWithAuthors = await db
  .select({
    post: posts,
    author: users
  })
  .from(posts)
  .innerJoin(users, eq(posts.authorId, users.id))
  .where(eq(posts.published, true))
  .orderBy(desc(posts.createdAt))

// Insert
const [newUser] = await db
  .insert(users)
  .values({
    email: 'new@example.com',
    name: 'New User'
  })
  .returning()

// Update
await db
  .update(users)
  .set({ name: 'Updated' })
  .where(eq(users.id, 1))

// Delete
await db
  .delete(users)
  .where(eq(users.id, 1))

// Search
const searchResults = await db
  .select()
  .from(users)
  .where(like(users.name, '%john%'))

Prisma

Setup

Code
Bash
npm install prisma @prisma/client
npm install @prisma/adapter-libsql @libsql/client
npx prisma init
prisma/schema.prisma
Prisma
// prisma/schema.prisma
generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
}
TSlib/prisma.ts
TypeScript
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
import { PrismaLibSQL } from '@prisma/adapter-libsql'
import { createClient } from '@libsql/client'

const libsql = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!
})

const adapter = new PrismaLibSQL(libsql)

export const prisma = new PrismaClient({ adapter })
Code
Bash
# Push schema to Turso
npx prisma db push

# Generate client
npx prisma generate
Code
TypeScript
// Usage
import { prisma } from '@/lib/prisma'

// Create
const user = await prisma.user.create({
  data: {
    email: 'user@example.com',
    name: 'John'
  }
})

// Read with relations
const posts = await prisma.post.findMany({
  where: { published: true },
  include: { author: true }
})

// Update
await prisma.user.update({
  where: { id: 1 },
  data: { name: 'Updated' }
})

// Delete
await prisma.post.delete({
  where: { id: 1 }
})

Database groups

Groups let you manage multiple databases together:

Code
Bash
# Create a group
turso group create my-group --location waw

# Add a database to the group
turso db create my-db --group my-group

# All databases in the group inherit locations
turso group locations add my-group fra

# List groups
turso group list

# Group-level tokens
turso group tokens create my-group

Multi-tenant architecture

Code
TypeScript
// Each tenant has its own database in the group
async function createTenantDb(tenantId: string) {
  // Use the Turso Platform API
  const response = await fetch(
    `https://api.turso.tech/v1/organizations/${orgId}/databases`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${TURSO_API_TOKEN}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: `tenant-${tenantId}`,
        group: 'tenants-group'
      })
    }
  )

  return response.json()
}

// Route to the correct database
function getTenantDb(tenantId: string) {
  return createClient({
    url: `libsql://tenant-${tenantId}-${org}.turso.io`,
    authToken: process.env.TURSO_AUTH_TOKEN
  })
}

Schema management

Turso CLI schema

Code
Bash
# Show schema
turso db shell my-database ".schema"

# Export schema
turso db shell my-database ".schema" > schema.sql

# Import schema
turso db shell my-database < schema.sql

Migrations with Drizzle

Code
Bash
# Structure
migrations/
├── 0000_init.sql
├── 0001_add_posts.sql
└── 0002_add_comments.sql
TSmigrate.ts
TypeScript
// migrate.ts
import { migrate } from 'drizzle-orm/libsql/migrator'
import { db } from './db'

async function main() {
  console.log('Running migrations...')
  await migrate(db, { migrationsFolder: './drizzle' })
  console.log('Migrations complete!')
}

main()

Schema dump and restore

Code
Bash
# Dump the entire database
turso db shell my-database ".dump" > backup.sql

# Restore
turso db create my-database-restored
turso db shell my-database-restored < backup.sql

Integrations

Next.js App Router

TSapp/api/users/route.ts
TypeScript
// app/api/users/route.ts
import { db } from '@/lib/db'
import { users } from '@/lib/db/schema'
import { NextResponse } from 'next/server'

export async function GET() {
  const allUsers = await db.select().from(users)
  return NextResponse.json(allUsers)
}

export async function POST(request: Request) {
  const data = await request.json()

  const [user] = await db
    .insert(users)
    .values(data)
    .returning()

  return NextResponse.json(user, { status: 201 })
}

Vercel Edge Functions

TSapp/api/edge/route.ts
TypeScript
// app/api/edge/route.ts
import { createClient } from '@libsql/client/web'

export const runtime = 'edge'

const db = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!
})

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const id = searchParams.get('id')

  const result = await db.execute({
    sql: 'SELECT * FROM users WHERE id = ?',
    args: [id]
  })

  return Response.json(result.rows[0])
}

SvelteKit

TSsrc/lib/db.ts
TypeScript
// src/lib/db.ts
import { createClient } from '@libsql/client'
import { TURSO_DATABASE_URL, TURSO_AUTH_TOKEN } from '$env/static/private'

export const db = createClient({
  url: TURSO_DATABASE_URL,
  authToken: TURSO_AUTH_TOKEN
})
TSsrc/routes/api/users/+server.ts
TypeScript
// src/routes/api/users/+server.ts
import { db } from '$lib/db'
import { json } from '@sveltejs/kit'
import type { RequestHandler } from './$types'

export const GET: RequestHandler = async () => {
  const result = await db.execute('SELECT * FROM users')
  return json(result.rows)
}

Remix

TSapp/utils/db.server.ts
TypeScript
// app/utils/db.server.ts
import { createClient } from '@libsql/client'

export const db = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!
})
TSapp/routes/users.tsx
TypeScript
// app/routes/users.tsx
import { json } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { db } from '~/utils/db.server'

export async function loader() {
  const result = await db.execute('SELECT * FROM users')
  return json({ users: result.rows })
}

export default function Users() {
  const { users } = useLoaderData<typeof loader>()

  return (
    <ul>
      {users.map((user: any) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

GitHub Actions CI/CD

.github/workflows/migrate.yml
YAML
# .github/workflows/migrate.yml
name: Database Migration

on:
  push:
    branches: [main]
    paths: ['drizzle/**']

jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci

      - name: Run migrations
        run: npx drizzle-kit push:sqlite
        env:
          TURSO_DATABASE_URL: ${{ secrets.TURSO_DATABASE_URL }}
          TURSO_AUTH_TOKEN: ${{ secrets.TURSO_AUTH_TOKEN }}

Performance tips

Indexes

Code
SQL
-- Create indexes for frequently queried columns
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_author ON posts(author_id);
CREATE INDEX idx_posts_created ON posts(created_at DESC);

-- Composite index
CREATE INDEX idx_posts_author_published
ON posts(author_id, published);

Query optimization

Code
TypeScript
// ❌ Fetch everything
const users = await db.execute('SELECT * FROM users')

// ✅ Fetch only the columns you need
const users = await db.execute('SELECT id, name FROM users')

// ❌ No limit
const posts = await db.execute('SELECT * FROM posts ORDER BY created_at DESC')

// ✅ With a limit
const posts = await db.execute({
  sql: 'SELECT * FROM posts ORDER BY created_at DESC LIMIT ?',
  args: [20]
})

// ✅ Pagination
const posts = await db.execute({
  sql: 'SELECT * FROM posts ORDER BY id LIMIT ? OFFSET ?',
  args: [20, page * 20]
})

Connection reuse

Code
TypeScript
// ❌ New client on every request
export async function handler() {
  const db = createClient({ ... })
  // ...
}

// ✅ Singleton pattern
let db: ReturnType<typeof createClient> | null = null

export function getDb() {
  if (!db) {
    db = createClient({
      url: process.env.TURSO_DATABASE_URL!,
      authToken: process.env.TURSO_AUTH_TOKEN!
    })
  }
  return db
}

Batch vs multiple queries

Code
TypeScript
// ❌ Multiple roundtrips
await db.execute({ sql: 'INSERT INTO users ...', args: [...] })
await db.execute({ sql: 'INSERT INTO users ...', args: [...] })
await db.execute({ sql: 'INSERT INTO users ...', args: [...] })

// ✅ Single roundtrip
await db.batch([
  { sql: 'INSERT INTO users ...', args: [...] },
  { sql: 'INSERT INTO users ...', args: [...] },
  { sql: 'INSERT INTO users ...', args: [...] }
])

Pricing

Starter (Free)

  • Storage: 9GB total
  • Databases: 500
  • Row reads: 1 billion/month
  • Row writes: 25 million/month
  • Locations: 3 replicas
  • Groups: 3

Scaler ($29/month)

  • Storage: 24GB (+ $0.75/GB)
  • Databases: Unlimited
  • Row reads: Unlimited
  • Row writes: 100 million (+ $1/million)
  • Locations: 6 replicas
  • Groups: Unlimited
  • Point-in-time recovery

Enterprise (Custom)

  • Dedicated support
  • SLA
  • Custom limits
  • SSO/SAML
  • Audit logs

Best practices

Development workflow

Code
Bash
# 1. Local database for development
turso db create my-app-dev

# 2. Staging
turso db create my-app-staging

# 3. Production
turso db create my-app-prod --location waw
turso db replicate my-app-prod fra
turso db replicate my-app-prod iad

Environment variables

.env.local
ENV
# .env.local (development)
TURSO_DATABASE_URL=libsql://my-app-dev-username.turso.io
TURSO_AUTH_TOKEN=eyJhbGci...

# .env.production
TURSO_DATABASE_URL=libsql://my-app-prod-username.turso.io
TURSO_AUTH_TOKEN=eyJhbGci...

Error handling

Code
TypeScript
import { db } from '@/lib/db'

async function getUser(id: number) {
  try {
    const result = await db.execute({
      sql: 'SELECT * FROM users WHERE id = ?',
      args: [id]
    })

    if (result.rows.length === 0) {
      return null
    }

    return result.rows[0]
  } catch (error) {
    if (error instanceof Error) {
      console.error('Database error:', error.message)

      // Retry logic for network errors
      if (error.message.includes('network')) {
        // Retry...
      }
    }
    throw error
  }
}

Type safety

TStypes/db.ts
TypeScript
// types/db.ts
interface User {
  id: number
  email: string
  name: string | null
  createdAt: string
}

// Typed query
async function getUsers(): Promise<User[]> {
  const result = await db.execute('SELECT * FROM users')
  return result.rows as User[]
}

// Or with Drizzle (full type safety)
import { db } from '@/lib/db'
import { users } from '@/lib/db/schema'

const allUsers = await db.select().from(users)
// allUsers is automatically typed!

FAQ - frequently asked questions

When to use embedded replicas?

When you have a read-heavy workload and ultra-low latencies matter to you. Ideal for e-commerce (products), CMS (articles), and analytics dashboards.

Does Turso support foreign keys?

Yes! Unlike PlanetScale, Turso/libSQL fully supports SQLite foreign keys.

How does embedded replica synchronization work?

The embedded replica syncs with the primary when you call db.sync(). You can also set syncInterval for automatic synchronization.

Can I use Turso with an existing SQLite database?

Yes! You can import an existing SQLite database into Turso via the CLI or API.

What is the difference between Turso and D1 (Cloudflare)?

D1 only works on Cloudflare Workers. Turso is platform-independent and offers embedded replicas along with more edge locations.

Does Turso support full-text search?

Yes, through the SQLite FTS5 extension. libSQL supports all standard SQLite extensions.

Code
SQL
-- FTS5 example
CREATE VIRTUAL TABLE posts_fts USING fts5(title, content);
INSERT INTO posts_fts SELECT title, content FROM posts;
SELECT * FROM posts_fts WHERE posts_fts MATCH 'search query';

Summary

Turso is an ideal database for modern edge applications:

  • Edge locations - data close to users
  • Embedded replicas - microsecond latencies
  • libSQL - open SQLite with superpowers
  • Serverless - zero infrastructure management
  • Cost-effective - significantly cheaper than alternatives

A great choice for applications that require low latencies, read-heavy workloads, and simple database architecture.