Choosing a Database
Durably supports multiple database backends through Kysely dialects. This guide helps you pick the right one.
At a Glance
SQLite Family (Single-Writer)
| Backend | Serverless | Setup | Performance | Cost |
|---|---|---|---|---|
| libSQL / Turso | Local: Limited, Remote: Yes | Zero config (local) / Managed (remote) | Fast local, edge-friendly remote | Free (local) / Free tier + pay-as-you-go (Turso) |
| better-sqlite3 | No | Zero config | Fast (synchronous) | Free |
| SQLocal (browser) | N/A | Zero config | Browser-local (OPFS) | Free (client-side) |
PostgreSQL (Multi-Writer)
- Serverless: Yes, depending on provider (Neon, Aurora Serverless, etc.)
- Setup: Managed service or self-hosted
- Performance: Strong under concurrency (advisory locks +
FOR UPDATE SKIP LOCKED) - Cost: Self-hosted or any managed provider — works with any PostgreSQL-compatible service
WARNING
Local SQLite (libSQL local, better-sqlite3) is single-writer — multiple workers on the same file will cause lock contention. Turso remote accepts multiple connections, but concurrency key enforcement is weaker than PostgreSQL (no advisory locks). For reliable multi-worker setups, use PostgreSQL.
Decision Flowchart
Running in the browser?
Yes → SQLocal (only option)
No ↓
Large volume of jobs, or multiple app servers sharing one DB?
(e.g. high-traffic API, queue with thousands of jobs/hour)
Yes → PostgreSQL (strongest guarantees)
Turso also works (weaker concurrency key enforcement)
No ↓
Deploying to serverless / edge (Vercel, Cloudflare)?
Yes → Turso (remote libSQL)
No ↓
Single server or CLI script?
Yes → libSQL (local) or better-sqlite3libSQL / Turso
Recommended default. libSQL works as a local embedded database and as a managed remote database via Turso. Same LibsqlDialect for both — just change the URL.
pnpm add @libsql/client @libsql/kysely-libsqlimport { createClient } from '@libsql/client'
import { LibsqlDialect } from '@libsql/kysely-libsql'
// Local (single file on disk)
const client = createClient({ url: 'file:local.db' })
// Turso remote (serverless / edge)
// const client = createClient({
// url: process.env.TURSO_DATABASE_URL!,
// authToken: process.env.TURSO_AUTH_TOKEN!,
// })
const dialect = new LibsqlDialect({ client })- Local: Single file, zero config, no external dependencies
- Turso: Managed service with global replication, free tier available
- Works on Vercel, Cloudflare Workers, Fly.io
TIP
See the fullstack-vercel-turso example for a complete Vercel + Turso deployment.
better-sqlite3
For CLI tools and scripts. Lightweight synchronous SQLite with prebuilt binaries for most platforms (may require build tools where prebuilds are unavailable).
pnpm add better-sqlite3import Database from 'better-sqlite3'
import { SqliteDialect } from 'kysely'
const dialect = new SqliteDialect({
database: new Database('local.db'),
})- Synchronous API (slightly faster for small workloads)
- Good for one-off scripts and CLI tools
- No remote support (use libSQL if you might need Turso later)
SQLite WAL Maintenance
For local SQLite backends using WAL (Write-Ahead Logging) mode, Durably automatically runs PRAGMA wal_checkpoint(TRUNCATE) during idle maintenance to prevent unbounded WAL file growth. This is throttled to at most once per 60 seconds and only runs when the worker is idle.
At migrate() time, Durably probes whether WAL checkpointing is supported. Checkpointing is enabled only for local file-backed SQLite — it is automatically skipped for Turso (remote libSQL), PostgreSQL, and browser (OPFS) backends.
PostgreSQL
For multi-worker production deployments. The recommended backend for running multiple workers concurrently, with advisory locks and FOR UPDATE SKIP LOCKED for strong concurrency guarantees.
pnpm add pgimport pg from 'pg'
import { PostgresDialect } from 'kysely'
const dialect = new PostgresDialect({
pool: new pg.Pool({
connectionString: process.env.DATABASE_URL,
}),
})- Multiple workers can poll and claim jobs safely (advisory locks + fencing tokens)
- Connection pooling via
pg.Pool - Works with any PostgreSQL-compatible service (Neon, Supabase, RDS, Cloud SQL, self-hosted, etc.)
SQLocal (Browser)
For browser-only apps. Runs SQLite in the browser using OPFS (Origin Private File System).
pnpm add sqlocalimport { SQLocalKysely } from 'sqlocal/kysely'
const { dialect } = new SQLocalKysely('app.sqlite3')- Data persists across page reloads (OPFS)
- Requires Secure Context (HTTPS or localhost)
- Single tab usage (OPFS exclusive lock)
- See the SPA Mode guide for React integration
Switching Backends
All backends use the same dialect parameter in createDurably(). To switch, just change the dialect — no other code changes needed:
const durably = createDurably({
dialect, // ← swap this
jobs: { myJob },
})The database schema is created automatically by durably.init() (or durably.migrate()). Durably does not support migrating data between backends.