The interface is localized. Skill descriptions and documents are machine-translated by AI into your language; code, commands, paths and technical terms stay in the original, and the author's document governs.
Explore skills
Data & research · SKILL.md

libsql

libsql

Connects JavaScript and TypeScript apps to libSQL, the open-source SQLite fork behind Turso, with the @libsql/client SDK: local files, in-memory databases, remote Turso databases over libsql:// or HTTP, and embedded replicas that sync from the cloud. Use when a user asks for SQLite with remote sync, an edge-friendly SQL database, a Turso setup, or batches, …

Independent GitHub snapshot · Automated classification · Original documentation governs

Tterminalskills / skillsApache-2.0

Skill documentation

GitHub

name: libsql description: >- Connects JavaScript and TypeScript apps to libSQL, the open-source SQLite fork behind Turso, with the @libsql/client SDK: local files, in-memory databases, remote Turso databases over libsql:// or HTTP, and embedded replicas that sync from the cloud. Use when a user asks for SQLite with remote sync, an edge-friendly SQL database, a Turso setup, or batches, transactions and parameterized queries with libSQL. license: Apache-2.0 compatibility: "Node.js 18+, Bun, Deno (via npm:). @libsql/client 0.18.x. Turso CLI for cloud databases." metadata: author: terminal-skills version: "1.1.0" category: development tags: ["libsql", "sqlite", "turso", "edge-database", "embedded"] repository: https://github.com/tursodatabase/libsql-client-ts

libSQL

Overview

libSQL is an open-source fork of SQLite that adds network access, replication and embedded replicas; Turso hosts it as a cloud database. The @libsql/client package (0.18.0 at the time of writing) talks to local SQLite files, in-memory databases and remote libSQL servers through one async API: execute, batch, transaction.

Turso also ships newer packages built on its Rust rewrite of SQLite: @tursodatabase/database (local, with concurrent writes), @tursodatabase/serverless (remote, no native dependencies) and @tursodatabase/sync. Turso's docs point to @libsql/client for ORM integration (Drizzle, Prisma) and for remote libSQL databases, and to @tursodatabase/sync instead of embedded replicas for new offline or bidirectional-sync work. Concurrent writes are not supported by @libsql/client.

Instructions

Installation and connection modes

npm install @libsql/client       # or: bun add @libsql/client
import { createClient } from "@libsql/client";

// Local SQLite file
const local = createClient({ url: "file:app.db" });

// In-memory (tests)
const memory = createClient({ url: ":memory:" });

// Turso cloud over libsql:// (WebSocket) or https:// (HTTP)
const cloud = createClient({
  url: process.env.TURSO_DATABASE_URL!,     // libsql://orders-prod-acme.turso.io
  authToken: process.env.TURSO_AUTH_TOKEN!,
});

Close a client with db.close() when finished. In edge runtimes without native bindings (Workers, Vercel Edge) import from @libsql/client/web, which supports only remote URLs, not file:.

Embedded replica

A local file that syncs from a remote Turso database: reads are local and fast, writes are sent to the remote primary.

const db = createClient({
  url: "file:replica.db",
  syncUrl: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!,
  syncInterval: 60,        // seconds between automatic syncs
});
await db.sync();           // pull the latest changes now

Embedded replicas need a persistent filesystem, so they do not fit serverless functions or Workers.

Queries

await db.execute(`CREATE TABLE IF NOT EXISTS posts (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  created_at INTEGER NOT NULL DEFAULT (unixepoch()))`);

const ins = await db.execute({
  sql: "INSERT INTO posts (title, slug) VALUES (?, ?)",
  args: ["Hello World", "hello-world"],
});
console.log(ins.lastInsertRowid, ins.rowsAffected);   // 1n 1

const found = await db.execute({ sql: "SELECT * FROM posts WHERE slug = :slug", args: { slug: "hello-world" } });
console.log(found.columns, found.rows[0].title);

Arguments are positional (? with an array) or named (:name with an object). A result has rows, columns, rowsAffected and lastInsertRowid. executeMultiple(sqlScript) runs a semicolon-separated script without parameters (migrations, seed files).

Batch and transactions

// One round trip, implicit transaction: all statements succeed or none do
const results = await db.batch([
  { sql: "INSERT INTO posts (title, slug) VALUES (?, ?)", args: ["Post 1", "post-1"] },
  { sql: "INSERT INTO posts (title, slug) VALUES (?, ?)", args: ["Post 2", "post-2"] },
  "SELECT COUNT(*) AS total FROM posts",
], "write");
console.log(results[2].rows[0].total);

// Interactive transaction: use when later statements depend on earlier results
const tx = await db.transaction("write");
try {
  await tx.execute({ sql: "UPDATE accounts SET balance = balance - ? WHERE user_id = ?", args: [100, 1] });
  await tx.execute({ sql: "UPDATE accounts SET balance = balance + ? WHERE user_id = ?", args: [100, 2] });
  await tx.commit();
} catch (err) {
  await tx.rollback();
  throw err;
} finally {
  tx.close();
}

Modes for batch and transaction: "write", "read" (read-only) and "deferred" (starts as a read, upgrades on first write). An interactive transaction holds a lock on the database, with a 5-second timeout on Turso, so keep it short.

Turso CLI

brew install tursodatabase/tap/turso          # macOS; Linux/WSL installer: docs.turso.tech/cli/installation
turso auth login
turso db create orders-prod
turso db show orders-prod --url               # libsql://orders-prod-<org>.turso.io
turso db tokens create orders-prod --read-only --expiration 7d
turso db shell orders-prod

turso db tokens create without flags makes a full-access token; prefer --read-only for read paths and an expiration for anything shared.

Examples

Example 1: "Add a Turso database to my Next.js app"

npm install @libsql/client
turso db create storefront-prod
turso db show storefront-prod --url
turso db tokens create storefront-prod
// lib/db.ts
import { createClient } from "@libsql/client";

if (!process.env.TURSO_DATABASE_URL) throw new Error("TURSO_DATABASE_URL is required");

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

Put both values in .env.local. A query such as db.execute("SELECT COUNT(*) AS n FROM products") returns { n: 42 } in rows[0].

Example 2: "Insert a row and get it back"

const created = await db.execute({
  sql: "INSERT INTO posts (title, slug) VALUES (?, ?) RETURNING id, title, slug",
  args: ["Launch notes", "launch-notes"],
});
console.log(created.rows[0]);   // { id: 7, title: 'Launch notes', slug: 'launch-notes' }

With RETURNING, the new row comes back in rows; lastInsertRowid is undefined and rowsAffected is 0 on a local file (checked on 0.18.0), so read the id from the row.

Guidelines

  • Always pass values through args; never concatenate user input into SQL.
  • lastInsertRowid is a bigint for plain inserts; use Number() for small ids. Integers above 2^53 in a result throw RangeError unless the client is created with intMode: "bigint" (or "string").
  • Errors are LibsqlError with a code such as SQLITE_CONSTRAINT; check err.code, for example to turn a duplicate slug into a 409 response.
  • Use batch() for independent writes that must succeed together; use an interactive transaction() only when a later statement depends on an earlier result.
  • Keep TURSO_AUTH_TOKEN in the environment or a secrets manager, never in source or a browser bundle.
  • Embedded replicas are eventually consistent; call db.sync() before a read that must see recent remote writes.
  • Remote databases take one writer at a time; for write-heavy or multi-writer workloads, check Turso's newer @tursodatabase/* packages first.
  • ":memory:" gives every test an isolated database.
Saved skills