Diesel
Diesel is a type-safe ORM and query builder that catches many SQL mistakes at compile time through schema codegen.
Recipe
Quick-reference recipe card - copy-paste ready.
use diesel::prelude::*;
#[derive(Queryable, Selectable)]
#[diesel(table_name = users)]
struct User { id: i64, email: String }
let results = users::table
.filter(users::active.eq(true))
.select(User::as_select())
.load(&mut conn)?;When to reach for this: You want compile-time schema alignment, a rich query builder, and migrations integrated with the ORM - often in sync or spawn_blocking architectures.
Working Example
use diesel::prelude::*;
use diesel::pg::PgConnection;
diesel::table! {
items (id) {
id -> Int8,
name -> Text,
}
}
#[derive(Queryable, Selectable, Insertable)]
#[diesel(table_name = items)]
struct Item { id: i64, name: String }
#[derive(Insertable)]
#[diesel(table_name = items)]
struct NewItem<'a> { name: &'a str }
fn main() -> QueryResult<()> {
let mut conn = PgConnection::establish(&std::env::var("DATABASE_URL").unwrap())?;
let new = NewItem { name: "widget" };
let inserted: Item = diesel::insert_into(items::table)
.values(&new)
.get_result(&mut conn)?;
let all: Vec<Item> = items::table.load(&mut conn)?;
println!("{:?} {:?}", inserted, all);
Ok(())
}What this demonstrates:
table!macro defines schema at compile time.- Separate
NewItemfor inserts withoutid. insert_into(...).get_resultreturns inserted row on PostgreSQL.Queryable+Selectablefor reads.
Deep Dive
How It Works
diesel print-schemageneratesschema.rsfrom live database.- Query builder methods compile to parameterized SQL.
- Connections are synchronous - use
spawn_blockingin async Axum handlers ordiesel-asyncfor native async.
Diesel vs sqlx
| Topic | Diesel | sqlx |
|---|---|---|
| SQL style | Builder + types | Raw SQL strings |
| Async | diesel-async addon | Native |
| Compile checks | Schema + queries | query! macro |
| Learning curve | Steeper | SQL-first |
Rust Notes
// Async with diesel-async (conceptual)
// let users = users::table.load(&mut async_conn).await?;Gotchas
- Blocking in async runtime - Sync
PgConnectionin async handler blocks Tokio. Fix:tokio::task::spawn_blockingordiesel-async. - Schema drift - Forgot to rerun
diesel migration runand regenerate schema. Fix: Automate in CI. - Complex SQL - Dynamic filters awkward in builder. Fix: Fall back to
sql_querywith bind params. - Connection per request - Establishing connections is expensive. Fix:
r2d2pool with Diesel. - N+1 with Diesel - Lazy loading pattern not like Rails - still manual. Fix:
joinorbelonging_toloads.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| sqlx | Async-first, raw SQL | You want ORM-level type safety |
| SeaORM | Async ORM with Diesel-like ergonomics | Minimal dependency tree |
tokio-postgres | Low-level control | Type-safe queries without macros |
FAQs
Diesel async status?
diesel-async provides async connections for PostgreSQL/MySQL.
Migrations?
diesel migration generate + diesel migration run - parallel to sqlx migrate.
SQLite?
Supported for dev/tests; PostgreSQL common in production.
Associations?
belonging_to, has_many macros with explicit joins.
Upsert?
on_conflict DSL on PostgreSQL and SQLite.
Raw SQL?
sql_query with bind for escape hatch.
Boxable queries?
BoxedQuery for dynamic filters in search endpoints.
Serde integration?
Map Queryable models to API DTOs at handler boundary.
Testing?
In-memory SQLite or testcontainers PostgreSQL.
New project default?
sqlx for async microservices; Diesel for schema-heavy domains with sync tolerance.
Related
- sqlx - Async SQL alternative
- SeaORM - Async ORM
- Migrations - Schema workflow
- Query Patterns & N+1 - Efficient queries
- Databases Best Practices - Data hygiene
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.