Turso Tutorial: Getting Started with Edge SQLite (libSQL)
A hands-on walkthrough of Turso and libSQL, from CLI setup to embedded replicas that put SQLite reads next to your users.
Key takeaways
- Install the Turso CLI, run turso db create, then copy the libsql:// URL and an auth token into @libsql/client.
- For fast reads, point the client at a local file with syncUrl set to the remote database, which makes it an embedded replica.
- Writes still go to one remote primary, so plan around that.
On this page
To start with Turso, install the CLI, run turso db create, grab the libsql:// URL and a token, and query it from @libsql/client. For low-latency reads, give the client a local file: URL plus a syncUrl: that turns it into an embedded replica, a real SQLite file that syncs from the remote primary.
Verified against
Turso CLI v1.0.34, @libsql/client 0.18.0, against the docs.turso.tech CLI, embedded-replica and local-development docs and the turso.tech pricing page. Checked October 2026.
Turso is a managed service built on libSQL, an open-source fork of SQLite that adds a network protocol (sqld) and a sync engine. The mental model that matters: one remote primary takes writes, and local or embedded copies serve reads. Everything below follows from that. For where it sits among the alternatives, see our serverless and edge databases guide.
Install the CLI and get a database
The install script is the documented path on macOS and Linux; Homebrew works too (brew install tursodatabase/tap/turso). Windows needs WSL.
$ curl -sSfL https://get.tur.so/install.sh | bash
$ turso auth signup # or: turso auth login
$ turso auth whoami
$ turso db create edge-demo
$ turso db show edge-demo --url
libsql://edge-demo-<org>.turso.io
$ turso db tokens create edge-demo
eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...
$ turso db shell edge-demo
Open a new shell after installing so turso is on your PATH. Store the URL and token as TURSO_DATABASE_URL and TURSO_AUTH_TOKEN. The token is a bearer credential; never commit it, and create one per app so you can revoke them independently.
Connect from Node and TypeScript
$ npm install @libsql/client
$ npm install -D tsx
// db.ts
import { createClient } from "@libsql/client";
const db = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});
await db.execute(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY,
body TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)`);
// Bound parameters, never string interpolation
await db.execute({
sql: "INSERT INTO notes (body) VALUES (?)",
args: ["First note from the edge"],
});
const result = await db.execute("SELECT id, body, created_at FROM notes");
console.table(result.rows);
$ TURSO_DATABASE_URL=libsql://edge-demo-<org>.turso.io TURSO_AUTH_TOKEN=... npx tsx db.ts
Every execute here is a network round trip to the primary. Fine for a script; for a read-heavy app you want the data on local disk. As of October 2026, Turso's TypeScript quickstart also points new projects at @tursodatabase/serverless for fetch-only runtimes. @libsql/client remains the client that supports embedded replicas, which is why we use it here.
Embedded replicas: the headline feature
An embedded replica is a local SQLite file that libSQL keeps in sync with the remote database. Reads hit the file at in-process speed. Writes go to the remote primary first, and on success the local file is updated.
// replica.ts
import { createClient } from "@libsql/client";
const db = createClient({
url: "file:local.db", // reads come from here
syncUrl: process.env.TURSO_DATABASE_URL!, // remote primary
authToken: process.env.TURSO_AUTH_TOKEN!,
syncInterval: 60, // background pull, seconds
});
await db.sync(); // pull before the first read-critical path
const rows = await db.execute("SELECT count(*) AS n FROM notes");
console.log(rows.rows[0].n);
Turso's docs make one point many tutorials get wrong: the replica that performed a write sees it immediately, without calling sync(). Other replicas (another server, another process with its own file) only see it after their next sync() or interval tick. So read-your-writes is safe within one instance and eventual across instances.
As of October 2026 the docs also recommend Turso Sync for new projects that need local-first writes with explicit push() and pull(), while describing embedded replicas as fully supported in production. If your app only needs fast reads, embedded replicas are the simpler choice.
Local development without the network
Two options. Point createClient at a plain file:dev.db and skip the network entirely, or run a local libSQL server that speaks the same protocol as production:
$ turso dev --db-file dev.db
const db = createClient({ url: "http://127.0.0.1:8080" });
Without --db-file, data is lost when the server stops. Turso does not ship a migration framework; because the engine is SQLite, Drizzle Kit, Prisma and Atlas work through the libSQL driver. Keep migrations as ordered SQL files and apply them to the primary. If you are weighing plain SQLite on a VM instead, our notes on running SQLite in production cover what you give up.
Pricing, as of October 2026
The turso.tech pricing page lists a Free plan with 100 databases, 5 GB of storage, 500 million rows read and 10 million rows written per month, and 3 GB of syncs. Developer is $4.99/month with unlimited databases and 9 GB of storage; Scaler is $24.92/month and Pro $416.58/month, both with overage pricing per GB and per row. The page marks these as discounted rates, which points to yearly billing. Unlimited databases on paid plans is what makes the database-per-tenant pattern practical.
Failure modes and the exact errors
SERVER_ERROR: Server returned HTTP status 401: the token is missing, expired, or for another database. Regenerate withturso db tokens create <db>and check that the env var is actually set in the process.URL_INVALIDor connection refused onlibsql://: you passed the HTTP URL from a dashboard copy or a typo. Use exactly whatturso db show <db> --urlprints.turso: command not foundafter install: the installer edited your shell profile; open a new shell.- Corrupted or locked
local.db: the docs warn not to open the replica file with another process while it is syncing. Do not pointsqlite3or a second client at the same file during sync. - Sync traffic bigger than expected: libSQL syncs 4 kB frames, so a one-byte row change still moves 4 kB, and B-tree splits or a deleted local file can trigger far larger re-syncs. Watch the syncs quota on the Free plan.
- Embedded replica fails on serverless: platforms without a writable filesystem cannot host the local file. Use the remote URL there.
Where this breaks
All writes go to one primary. If your writers are spread across continents, every far-away write pays the round trip, and the edge story applies only to reads. Heavy write workloads, multi-statement transactions spanning regions, and features SQLite lacks (row-level security, rich types, extensions you rely on in Postgres) all argue for a Postgres-class system instead. For the head-to-head with Cloudflare's take on the same idea, read Turso vs Cloudflare D1.
Questions people ask
Is Turso free to use?
Yes, within limits. As of October 2026 the Free plan includes 100 databases, 5 GB of storage, 500 million rows read and 10 million rows written per month, plus 3 GB of sync traffic, with no card required. Paid plans start at $4.99/month (yearly billing) for unlimited databases and higher quotas. Check the pricing page before designing around a limit, because quotas have changed over time.
What is the difference between Turso and libSQL?
libSQL is the open-source fork of SQLite that adds a server (sqld), a remote protocol and replication. Turso is the company and managed cloud that runs libSQL databases for you, with the CLI, auth tokens and billing. You can self-host libSQL, but then the hosting, backups and token issuance are your problem rather than Turso's.
Do embedded replicas give read-your-writes consistency?
Within one replica, yes. Turso's docs state that after a successful write, the replica that made the write sees the new data immediately, even without calling sync(). Other replicas only see it after their own sync() or the next syncInterval tick. If a user can hit a different instance on the next request, read from the primary or call sync() first.
The call we'd make
For read-heavy apps that want SQLite simplicity, Turso is a strong default, and embedded replicas are the reason. Put the primary near your writers, use embedded replicas where reads cluster, and treat cross-instance staleness as a design constraint from day one. If your app is write-heavy or needs cross-region transactional guarantees, pick Postgres and stop fighting the model.
Sources
Cite this article
Kiril Urbonas (2026). Turso Tutorial: Getting Started with Edge SQLite (libSQL). DevOpsNess. Updated 2026-10-31. https://www.devopsness.com/blog/turso-tutorial
[Turso Tutorial: Getting Started with Edge SQLite (libSQL)](https://www.devopsness.com/blog/turso-tutorial) (Kiril Urbonas, DevOpsNess, updated 2026-10-31)
More from Cloud
Explore more articles in this category
.NET 8 Support Ends November 10: The Lambda and Azure Functions Checklist
.NET 8, .NET 9, the Lambda dotnet8 runtime and the Azure Functions in-process model all lose support on November 10, 2026. Inventory commands, the isolated-worker migration and a CI gate.
Kubernetes 1.34 Hit End of Life Oct 27: What EKS, GKE and AKS Do Next
Upstream stopped patching 1.34 on October 27. EKS starts billing 6x on December 2, AKS drops it at the end of November, and GKE gives you until January. The dates, the commands to find your 1.34 clusters, and the upgrade order.
Lambda Python 3.10 Is Deprecated Today: Find Every Function and Move to 3.13
AWS deprecated the Lambda python3.10 runtime on October 31, 2026. Functions keep running, patches stop. The inventory commands, IaC diffs and AL2023 gotchas for moving to python3.13.
You might have missed
Evergreen posts worth revisiting.