Diesel (Rust) ORM: CLI, Async & vs SQLx Guide

Max WellsMax WellsFounder of Rustify

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.rs file (generated from migrations), derive Queryable/Insertable on your structs, and write queries using a Rust DSL that compiles to SQL. Unlike SQLx, Diesel is synchronous by default (async support via diesel-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 run

The 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.

DieselSQLx
Query styleRust DSL / query builderRaw SQL strings
Type checkingCompile-time (schema.rs)Compile-time (live DB at build)
AsyncVia diesel-async crateNative async
MigrationsBuilt-in CLIBuilt-in (sqlx migrate)
Learning curveHigher (DSL to learn)Lower (SQL you already know)
Complex queriesDSL can get verboseSQL is expressive
DB supportPostgres, MySQL, SQLitePostgres, 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


  • 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

Ready to Land a $80-120k Rust Job?