# Turso Tutorial: Getting Started with Edge SQLite (libSQL)

URL: https://www.devopsness.com/blog/turso-tutorial
Author: Kiril Urbonas
Published: 2026-08-06
Updated: 2026-10-31
Categories: Cloud
Tags: Database, Turso, Sqlite, Libsql, Edge

Summary: A hands-on walkthrough of Turso and libSQL, from CLI setup to embedded replicas that put SQLite reads next to your users.

---
# Turso Tutorial: Getting Started with Edge SQLite (libSQL)

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]
> 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](/blog/best-serverless-databases).

## 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
```

```ts
// 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.

```ts
// 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
```

```ts
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](/blog/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 with `turso db tokens create <db>` and check that the env var is actually set in the process.
- **`URL_INVALID` or connection refused on `libsql://`**: you passed the HTTP URL from a dashboard copy or a typo. Use exactly what `turso db show <db> --url` prints.
- **`turso: command not found`** after 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 point `sqlite3` or 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](/blog/turso-vs-cloudflare-d1-edge-sqlite).

## 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

- [Turso Docs: CLI installation](https://docs.turso.tech/cli/installation)
- [Turso Docs: Embedded replicas](https://docs.turso.tech/features/embedded-replicas/introduction)
- [Turso Docs: Local development with turso dev](https://docs.turso.tech/local-development)
- [Turso pricing](https://turso.tech/pricing)

