TL;DR: Diesel is a Rust ORM and query builder that generates type-safe SQL queries at compile time. You define your schema in a
schema.rsfile (generated from migrations), deriveQueryable/Insertableon your structs, and write queries using a Rust DSL that compiles to SQL. Unlike SQLx, Diesel is synchronous by default (async support viadiesel-async) and uses an ORM-style query builder rather than raw SQL.
What Is Diesel?
Diesel is a compile-time safe ORM; it catches type mismatches between your Rust code and SQL schema at compile time, before your application runs.
[dependencies]
diesel = { version = "2", features = ["postgres"] }
dotenvy = "0.15"use diesel::prelude::*;
// Your table schema (generated by diesel CLI from migrations)
diesel::table! {
users (id) {
id -> Integer,
name -> Text,
email -> Text,
}
}
// Rust struct maps to the table
#[derive(Queryable, Selectable, Debug)]
#[diesel(table_name = users)]
pub struct User {
pub id: i32,
pub name: String,
pub email: String,
}
fn get_users(conn: &mut PgConnection) -> Vec<User> {
users::table
.select(User::as_select())
.load(conn)
.expect("failed to load users")
}How Does the Diesel CLI Work?
diesel CLI manages migrations and generates the schema.rs file that Diesel uses for compile-time checks.
# Install the CLI
cargo install diesel_cli --no-default-features --features postgres
# Set up your database URL
echo DATABASE_URL=postgres://localhost/myapp > .env
# Create migration
diesel migration generate create_users
# Run migrations (also generates schema.rs)
diesel migration runThe generated schema.rs contains macro-generated table definitions that Diesel uses to type-check your queries.
How Do You Insert and Update Records?
Derive Insertable on a struct and use diesel::insert_into. For updates, use diesel::update with a AsChangeset struct.
#[derive(Insertable)]
#[diesel(table_name = users)]
pub struct NewUser<'a> {
pub name: &'a str,
pub email: &'a str,
}
fn create_user(conn: &mut PgConnection, name: &str, email: &str) -> User {
let new_user = NewUser { name, email };
diesel::insert_into(users::table)
.values(&new_user)
.returning(User::as_returning())
.get_result(conn)
.expect("error inserting user")
}
// Update
fn update_email(conn: &mut PgConnection, user_id: i32, new_email: &str) -> User {
diesel::update(users::table.find(user_id))
.set(users::email.eq(new_email))
.returning(User::as_returning())
.get_result(conn)
.expect("error updating user")
}How Is Diesel Different From SQLx?
Diesel uses a Rust query DSL (ORM-style). SQLx uses raw SQL strings with compile-time verification via a database connection at build time.
| Diesel | SQLx | |
|---|---|---|
| Query style | Rust DSL / query builder | Raw SQL strings |
| Type checking | Compile-time (schema.rs) | Compile-time (live DB at build) |
| Async | Via diesel-async crate | Native async |
| Migrations | Built-in CLI | Built-in (sqlx migrate) |
| Learning curve | Higher (DSL to learn) | Lower (SQL you already know) |
| Complex queries | DSL can get verbose | SQL is expressive |
| DB support | Postgres, MySQL, SQLite | Postgres, MySQL, SQLite, MSSQL |
Diesel is better when you want ORM-style abstractions and a query builder. SQLx is better when you prefer writing SQL directly and need native async.
How Do You Use Diesel Async?
The diesel-async crate adds async support on top of Diesel; the query DSL is identical, just .await added.
[dependencies]
diesel = { version = "2", features = ["postgres"] }
diesel-async = { version = "0.5", features = ["postgres", "bb8"] }use diesel_async::{AsyncPgConnection, RunQueryDsl};
async fn get_users(conn: &mut AsyncPgConnection) -> Vec<User> {
users::table
.select(User::as_select())
.load(conn)
.await
.expect("failed to load users")
}Frequently Asked Questions
For migrations, yes; diesel migration run generates schema.rs which is required for compile-time checks. Without the CLI, there's no type-safe schema. You can write migrations manually as SQL files.
Yes; use r2d2 (sync) or bb8/deadpool (async) pool integrations provided by the diesel and diesel-async crates.
Yes; diesel::sql_query("SELECT ...") runs raw SQL. You lose some type safety but it's useful for complex queries that don't fit the DSL.
For new async Rust projects, SQLx is often the simpler choice; you write SQL you already know, and it's natively async. Use Diesel when you want an ORM query builder, have a team familiar with Rails/ActiveRecord-style patterns, or need its specific compile-time guarantees.
Sources
- Diesel documentation: Official guides
- diesel-async crate: Async Diesel wrapper
- Diesel GitHub
Related Glossary Terms
- SQLx: The alternative: raw async SQL with compile-time checks
- Serde: Used alongside Diesel for JSON serialization
- Async/Await: Required for diesel-async
- Tokio: The runtime used with diesel-async
- Chrono: Diesel commonly maps SQL dates and timestamps into Chrono types
- SeaORM: SeaORM is the async ORM alternative to Diesel's compile-time model
Keep Reading
- Rust vs Go for Backend Development: database layer comparison between Rust and Go backends
- Rust on AWS Lambda: using a Rust ORM in a serverless context

