Turso offers two Rust crates:
| turso | libsql |
|---|
| Use case | Local / embedded database, sync | Remote access, existing libSQL codebases |
| Engine | Turso Database (rewrite) | libSQL (SQLite fork) |
| Concurrent writes | Yes (MVCC) | Not supported |
| Sync | push/pull (local-first) | Embedded Replicas (writes go to cloud primary) |
| C compiler | Not required | Required 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:
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 (libSQL)
The libsql crate is built on libSQL, the open-source fork of SQLite that powers Turso Cloud today. It is production-ready and battle-tested, and is the right choice when you are working with an existing libsql-based codebase.
With libsql Embedded Replicas, reads are local and writes are sent to the cloud primary, then reflected back to the replica. Embedded Replicas are fully supported. For new projects that need sync, we recommend the turso crate with turso::sync — see the quickstart.
Embedded Replicas
You can work with Embedded Replicas that sync from a Turso Cloud database to a local SQLite file. Reads run locally; writes are sent to the cloud primary and then reflected back to the replica:
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
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:
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:
| Feature | Description |
|---|
remote | HTTP-only client, pure Rust. No C compiler needed. |
core | Local database only. Requires a C compiler. |
replication | Combines core with embedded replica support. Requires a C compiler. |
encryption | Encryption 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?;