Skip to content

Quickstart

The shortest path from an empty project to a first query. Each step names the page that explains it in depth; this one only gets you running.

Add the crates

Cargo.toml
[dependencies]
turso-orm = "0.1"
tokio     = { version = "1", features = ["macros", "rt-multi-thread"] }

The engine is part of the crate: there is nothing to install or start. Feature flags are listed on the compatibility page.

Open a database

The first decision is where the data lives. Four modes, one line each:

use turso_orm::prelude::*;

let db = Database::connect(ConnectOptions::in_memory()).await?;

A database that lives as long as the pool and vanishes with it. For tests, prototypes and short-lived agents.

use turso_orm::prelude::*;

let db = Database::connect(ConnectOptions::new("app.db")).await?;

A file next to the binary, created on first open. For desktop tools, services with local state and anything that must survive a restart.

use turso_orm::prelude::*;

let db = Database::connect(
    ConnectOptions::sync("replica.db", "libsql://<db>.turso.io").auth_token(token),
)
.await?;

A local file kept in sync with a Turso Cloud database: reads stay local, writes are forwarded. Needs the sync feature.

use turso_orm::prelude::*;

let db = Database::connect(
    ConnectOptions::remote("libsql://<db>.turso.io").auth_token(token),
)
.await?;

No disk at all: every statement goes over HTTP. For functions at the edge. Needs the serverless feature.

Database is a pool of engine connections, cheap to clone and safe to share. Entities, queries, transactions and migrations are the same in all four modes; only this line changes. Tokens, encryption and the other options are on the compatibility page, transactions under Transactions.

Describe a table

/// The `user` entity.
mod user {
    use turso_orm::prelude::*;

    /// A row of the `user` table.
    #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
    #[turso(table_name = "user")]
    pub(crate) struct Model {
        #[turso(primary_key)]
        pub id: i32,
        #[turso(unique)]
        pub email: String,
        pub name: Option<String>,
    }

    /// The relations of `user`; there are none.
    #[derive(Copy, Clone, Debug, DeriveRelation)]
    pub(crate) enum Relation {}

    impl ActiveModelBehavior for ActiveModel {}
}

One struct per table, in a module named after it. The derive generates Entity, Column, PrimaryKey and ActiveModel from it; the empty Relation enum is where joins will be declared. Attributes and column types are on the entities page.

Create the table

db.execute(
    Schema::new()
        .create_table_from_entity(user::Entity)
        .to_statement(),
)
.await?;

Good enough for a first run and for tests. An application keeps a history of its schema instead; see Migrations.

Write and read

    // `id` stays `NotSet`, so the database assigns it and `insert` returns
    // the stored row with the generated key.
    let alice = user::ActiveModel {
        email: Set("alice@example.com".into()),
        name: Set(Some("Alice".into())),
        ..Default::default()
    }
    .insert(&db)
    .await?;
    println!("inserted {alice:?}");

    let found = user::Entity::find()
        .filter(user::Column::Email.contains("example"))
        .one(&db)
        .await?;
    println!("found {found:?}");

Set marks the fields to write and insert returns the stored row with its generated id. find starts a query, filter narrows it with the generated Column enum, one runs it. Everything the builder can do is on the queries page, and the examples on GitHub show complete programs, including two web APIs.