Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.turso.tech/llms.txt

Use this file to discover all available pages before exploring further.

Turso offers two Rust crates:
tursolibsql (legacy)
Use caseLocal / embedded database, syncRemote access, legacy embedded replicas
EngineTurso Database (rewrite)libSQL (SQLite fork)
Concurrent writesYes (MVCC)Not supported
Syncpush/pull (true local-first)Embedded replicas (writes go to remote)
C compilerNot requiredRequired for core, replication, encryption features
Starting a new project? Use turso for local/embedded use or sync. Use libsql with the remote feature for over-the-wire access (no C compiler needed).

turso

For local and embedded use. Built on the Turso Database engine with concurrent writes (MVCC) and async I/O.

Installing

cargo add turso tokio --features tokio/full

Connecting

use turso::Builder;

let db = Builder::new_local("app.db").build().await?;
let conn = db.connect()?;
In-memory databases are also supported:
let db = Builder::new_local(":memory:").build().await?;

Querying

conn.execute(
    "CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL
    )",
    (),
).await?;

conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",)).await?;

let mut rows = conn.query("SELECT * FROM users", ()).await?;
while let Some(row) = rows.next().await? {
    let id: i64 = row.get(0)?;
    let name: String = row.get(1)?;
    println!("User: {} {}", id, name);
}

Prepared Statements

let mut stmt = conn.prepare("SELECT * FROM users WHERE id = ?1").await?;
let mut rows = stmt.query([42]).await?;

Transactions

let tx = conn.transaction().await?;

tx.execute("INSERT INTO users (name) VALUES (?1)", ["Alice"]).await?;
tx.execute("INSERT INTO users (name) VALUES (?1)", ["Bob"]).await?;

tx.commit().await?;

Encryption

Encrypt local databases at rest:
use turso::{Builder, EncryptionOpts};

let db = Builder::new_local("encrypted.db")
    .experimental_encryption(true)
    .with_encryption(EncryptionOpts {
        cipher: "aegis256".to_string(),
        hexkey: "b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327".to_string(),
    })
    .build()
    .await?;
Supported ciphers: aegis256, aegis256x2, aegis128l, aegis128x2, aegis128x4, aes256gcm, aes128gcm. Encrypted databases cannot be read as standard SQLite databases — you must use the Turso Database engine to open them.
Turso Cloud databases can also be encrypted with bring-your-own-key — learn more.

Sync (Push and Pull)

For local database with cloud sync. All reads and writes happen locally; use push() to send changes to the cloud and pull() to fetch remote changes. Enable the sync feature:
cargo add turso --features sync
use turso::sync::Builder;

let db = Builder::new_remote("app.db")
    .with_remote_url(&std::env::var("TURSO_DATABASE_URL")?)
    .with_auth_token(&std::env::var("TURSO_AUTH_TOKEN")?)
    .bootstrap_if_empty(true)  // Download schema on first sync (default)
    .build()
    .await?;

let conn = db.connect().await?;
On the first run, the local database is automatically bootstrapped from the remote. See Turso Sync for full details.

Push and Pull

// Push local writes to Turso Cloud
db.push().await?;

// Pull remote changes (returns true if changes were applied)
let changed = db.pull().await?;

Checkpoint

Compact the local WAL to bound disk usage while preserving sync state:
db.checkpoint().await?;

Stats

let stats = db.stats().await?;
println!("Network received: {} bytes", stats.network_received_bytes);
println!("Network sent: {} bytes", stats.network_sent_bytes);
println!("WAL size: {} bytes", stats.main_wal_size);

libsql (Remote)

Use the libsql crate with the remote feature for over-the-wire access to Turso Cloud. This uses pure Rust HTTP — no C compiler needed.

Installing

cargo add libsql --features remote

Connecting

use libsql::Builder;

let url = std::env::var("TURSO_DATABASE_URL")?;
let token = std::env::var("TURSO_AUTH_TOKEN")?;

let db = Builder::new_remote(url, token).build().await?;
let conn = db.connect()?;

Querying

let mut rows = conn.query("SELECT * FROM users WHERE id = ?1", [1]).await?;

libsql (Legacy)

The libsql crate is built on libSQL, our open-source fork of SQLite that predated the Turso Database engine. It is fully supported — if you run into something that doesn’t work yet with the turso crate, libsql is a reliable fallback.
With libsql embedded replicas, reads are local but writes go to the remote database. For true local-first writes with push/pull sync, use the turso crate with turso::sync — see the quickstart.

Embedded Replicas

Usage of embedded replicas is strongly discouraged. Use the turso crate with turso::sync instead — it is built on the Turso Database engine with better performance, true local-first writes, and concurrent write support.
You can work with embedded replicas that can sync from the remote database to a local SQLite file, and delegate writes to the remote primary database:
use libsql::Builder;

let url = std::env::var("TURSO_DATABASE_URL")?;
let token = std::env::var("TURSO_AUTH_TOKEN")?;

let db = Builder::new_remote_replica("local.db", url, token)
    .build()
    .await?;
let conn = db.connect()?;
Embedded Replicas only works where you have access to the file system.

Manual Sync

db.sync().await?;

Sync Interval

use std::time::Duration;

let db = Builder::new_remote_replica("local.db", url, token)
    .sync_interval(Duration::from_secs(300))
    .build()
    .await?;

Read Your Own Writes

By default, after a push(), the next pull() waits for the server to fully catch up with your changes before returning — guaranteeing you always read your own writes. This is safer but much slower, since the server must process all pending changes. If you can tolerate eventually-consistent reads, disable this for significantly faster pulls:
let db = Builder::new_remote_replica("local.db", url, token)
    .read_your_writes(false)
    .build()
    .await?;

Encryption

For new projects, we recommend the turso crate for local encryption — it is built on the Turso Database engine with better performance and concurrent write support.
To enable encryption on a SQLite file, pass the encryption key value as an argument to the constructor:
Rust
use libsql::Builder;
use bytes::Bytes;

let url = env::var("LIBSQL_URL").expect("LIBSQL_URL must be set");
let token = env::var("LIBSQL_AUTH_TOKEN").unwrap_or_default();

let cipher = Cipher::YourChosenCipher;
let encryption_key_bytes = Bytes::from("your_secure_encryption_key_here");

let encryption_config = EncryptionConfig {
    cipher,
    encryption_key: encryption_key_bytes,
};

let mut db = Builder::new_remote_replica("local.db", &url, &token)
  .encryption_config(encryption_config) // Apply encryption configuration
  .build()
  .await
  .unwrap();

let conn = db.connect().unwrap();
Encrypted databases appear as raw data and cannot be read as standard SQLite databases. You must use the libSQL client for any operations — learn more.

Conditional compilation

The libsql crate supports conditionally compiling features:
FeatureDescription
remoteHTTP-only client, pure Rust. No C compiler needed.
coreLocal database only. Requires a C compiler.
replicationCombines core with embedded replica support. Requires a C compiler.
encryptionEncryption at rest. Requires cmake. Not enabled by default.

Simple query

conn.execute("SELECT * FROM users", ()).await?;
conn.execute("SELECT * FROM users WHERE id = ?1", [1]).await?;

Placeholders

conn.execute("SELECT * FROM users WHERE id = ?1", libsql::params![1]).await?;

Deserialization

use libsql::{de, Builder};

#[derive(Debug, serde::Deserialize)]
struct User {
    name: String,
    age: i64,
}

let mut stmt = conn.prepare("SELECT * FROM users WHERE id = ?1").await?;
let row = stmt.query([1]).await?.next().await?.unwrap();
let user = de::from_row::<User>(&row)?;

Batch Transactions

conn.execute_batch(r#"
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
  );

  INSERT INTO users (name) VALUES ('Alice');
  INSERT INTO users (name) VALUES ('Bob');
"#).await?;

Interactive Transactions

let tx = conn.transaction().await?;

tx.execute("INSERT INTO users (name) VALUES (?1)", ["Alice"]).await?;
tx.execute("INSERT INTO users (name) VALUES (?1)", ["Bob"]).await?;

tx.commit().await?;