Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Database Setup

Opening a database connection in Toasty has two steps: register your models, then connect to a database. Db::builder() handles both.

let mut db = toasty::Db::builder()
    .models(toasty::models!(User, Post))
    .connect("sqlite::memory:")
    .await?;

Registering models

The models! macro builds a ModelSet — the collection of model definitions Toasty uses to generate the database schema. It accepts three forms, which can be combined freely:

toasty::models!(
    // All models from the current crate
    crate::*,
    // All models from an external crate
    third_party_models::*,
    // Individual models
    User,
    other_module::Post,
)

crate::* finds all #[derive(Model)] and #[derive(Embed)] types in your crate at compile time. This is the simplest option when all your models live in one crate.

You don’t need to list every model. Registering a model also registers any models reachable through its fields — BelongsTo, HasMany, HasOne, and embedded types are all discovered by traversing the model’s fields. For example, if User has a HasMany<Post> field and Post has a BelongsTo<User> field, toasty::models!(User) registers both User and Post.

Connection URLs

The connection URL determines which database driver Toasty uses. Each driver requires its corresponding feature flag in Cargo.toml.

SchemeDatabaseFeature flag
sqliteSQLitesqlite
postgresql or postgresPostgreSQLpostgresql
mysqlMySQLmysql
dynamodbDynamoDBdynamodb

Examples:

// In-memory SQLite
.connect("sqlite::memory:")

// SQLite file
.connect("sqlite:./path/to/db.sqlite")

// PostgreSQL
.connect("postgresql://user:pass@localhost:5432/mydb")

// MySQL
.connect("mysql://user:pass@localhost:3306/mydb")

// DynamoDB (uses AWS config from environment)
.connect("dynamodb://us-east-1")

For per-backend details — URL query parameters, TLS, type mapping, and per-driver behavior — see the Database Backends chapters.

Using a driver directly

If you need more control over the driver configuration, construct the driver yourself and pass it to build() instead of connect():

let driver = toasty_driver_sqlite::Sqlite::in_memory();
let mut db = toasty::Db::builder()
    .models(toasty::models!(User))
    .build(driver)
    .await?;

Connection pool

Db owns a connection pool. Each query checks out a connection from the pool for the duration of the call and returns it when finished. The pool defaults work for typical applications; the builder exposes knobs for tuning size, timeouts, and broken-connection recovery.

use std::time::Duration;

let mut db = toasty::Db::builder()
    .models(toasty::models!(crate::*))
    .max_pool_size(32)
    .pool_wait_timeout(Some(Duration::from_secs(5)))
    .pool_create_timeout(Some(Duration::from_secs(10)))
    .connect("postgresql://user:pass@localhost/mydb")
    .await?;
Builder methodDefaultPurpose
max_pool_size(n)num_cpus * 2Cap on simultaneous open connections. Drivers may enforce a lower cap (e.g., in-memory SQLite is single-connection).
pool_wait_timeout(Some(d))NoneMaximum time Db waits for a free connection before returning an error. None waits indefinitely.
pool_create_timeout(Some(d))NoneMaximum time to spend opening a new connection.
pool_health_check_interval(Some(d))Some(60s)How often the background sweep pings an idle connection to detect a silently-broken backend. None disables the sweep.
pool_pre_ping(true)falsePing every connection before handing it to the caller. Adds one round-trip per checkout in exchange for guaranteeing the caller sees a live connection.

Recovering from a backend restart

A database restart, a load-balancer-closed socket, or a backend session timeout leaves the pool holding TCP sockets that look open but reject the next query. Toasty handles this two ways:

  • Background sweep. Every pool_health_check_interval, the pool pings one idle connection. If the ping fails, the pool drops the failing connection and eagerly pings the rest of the idle slots so a single bad result drains every dead connection in one pass.
  • Reactive sweep. When a user query observes a connection-lost error, the same eager sweep runs immediately. A backend restart typically costs one failed user query rather than one per pooled connection.

Enable pool_pre_ping(true) if even one failed query is unacceptable — for example, a public API behind a flaky network or an idempotent worker without retry. The cost is one extra round-trip per checkout.

Table name prefix

To prefix all generated table names (useful when multiple services share a database), call table_name_prefix() on the builder:

let mut db = toasty::Db::builder()
    .models(toasty::models!(crate::*))
    .table_name_prefix("myapp_")
    .connect("sqlite::memory:")
    .await?;