Diesel

Diesel is the most mature safe and extensible ORM and query builder in the Rust ecosystem. Featuring compile-time type checking, extensive query validation, and established performance, it maximizes Rust's ownership system and type system to guarantee database operation safety. From thin SQLite access wrappers to complex join queries, it supports a wide range of use cases and provides an innovative approach that eliminates runtime errors by verifying SQL syntax and schema consistency at compile time.

ORMRustType SafetyQuery BuilderCompile-time VerificationSQLitePostgreSQLMySQL

GitHub Overview

diesel-rs/diesel

A safe, extensible ORM and Query Builder for Rust

Stars13,477
Watchers110
Forks1,137
Created:August 29, 2015
Language:Rust
License:Apache License 2.0

Topics

mysqlormpostgresqlquery-builderrustsqlite

Star History

diesel-rs/diesel Star History
Data as of: 7/17/2025, 10:32 AM

Library

Diesel

Overview

Diesel is the most mature safe and extensible ORM and query builder in the Rust ecosystem. Featuring compile-time type checking, extensive query validation, and established performance, it maximizes Rust's ownership system and type system to guarantee database operation safety. From thin SQLite access wrappers to complex join queries, it supports a wide range of use cases and provides an innovative approach that eliminates runtime errors by verifying SQL syntax and schema consistency at compile time.

Details

Diesel 2025 maintains its position as the veteran champion in the Rust ORM field. With diesel-async providing modern asynchronous support, it can now handle contemporary web service development with tokio/axum-based stacks. It fully supports the three major databases (PostgreSQL, MySQL, SQLite) and comprehensively provides enterprise development features such as migration management, connection pooling, batch operations, and transaction processing. The design that guarantees query type safety through Rust's powerful type system significantly reduces runtime errors, achieving robustness that's difficult to realize with ORMs in other languages.

Key Features

  • Compile-time Type Checking: Validates SQL query and schema consistency at compile time
  • Zero-cost Abstraction: Performance optimization through Rust's zero-cost abstractions
  • Extensive Database Support: PostgreSQL, MySQL, SQLite compatibility
  • Migration Management: Automated application of version-controlled schema changes
  • Query Builder: Type-safe with complete protection against SQL injection attacks
  • Async Support: Modern asynchronous performance with diesel-async

Pros and Cons

Pros

  • Complete prevention of SQL runtime errors through Rust's compile-time safety
  • Significant reduction of boilerplate code through advanced type inference
  • Efficient migration and schema management with diesel CLI tools
  • Type-safe implementation of complex join queries and subqueries possible
  • Superior performance over other ORMs through zero-cost abstractions
  • Rich community support and detailed documentation
  • Complete support for PostgreSQL-specific features (arrays, JSON, custom types)

Cons

  • High learning curve for Rust beginners, requiring understanding of complex type systems
  • Dynamic query generation is complex due to schema-first design
  • Compile time tends to increase proportionally with query complexity
  • Integration with other language ecosystems is difficult, requires Rust-only environment
  • Additional effort required for setup and maintenance of documentation generation tools
  • Support for new SQL features may take time

Reference Pages

Code Examples

Project Setup and Cargo Configuration

# Cargo.toml
[dependencies]
diesel = { version = "2.1", features = ["postgres", "chrono", "uuid"] }
diesel_migrations = "2.1"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1.0", features = ["v4", "serde"] }
tokio = { version = "1.0", features = ["full"] }

# For async support
diesel-async = { version = "0.4", features = ["postgres", "deadpool"] }
deadpool = { version = "0.10", features = ["managed"] }

[dev-dependencies]
diesel_cli = "2.1"
# Install Diesel CLI
cargo install diesel_cli --no-default-features --features postgres

# Initialize project
diesel setup

# Create migration
diesel migration generate create_users_table

# Run migration
diesel migration run

# Generate schema file
diesel print-schema > src/schema.rs

Database Schema Definition and Migration

-- migrations/2024_01_01_000000_create_users_table/up.sql
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR NOT NULL,
    email VARCHAR UNIQUE NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_email ON users(email);

-- migrations/2024_01_01_000000_create_users_table/down.sql
DROP TABLE users;
-- migrations/2024_01_02_000000_create_posts_table/up.sql
CREATE TABLE posts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    title VARCHAR NOT NULL,
    content TEXT NOT NULL,
    author_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    published BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_posts_author_id ON posts(author_id);
CREATE INDEX idx_posts_published ON posts(published);

-- migrations/2024_01_02_000000_create_posts_table/down.sql
DROP TABLE posts;
// src/schema.rs (auto-generated by diesel print-schema)
diesel::table! {
    users (id) {
        id -> Uuid,
        name -> Varchar,
        email -> Varchar,
        created_at -> Timestamp,
        updated_at -> Timestamp,
    }
}

diesel::table! {
    posts (id) {
        id -> Uuid,
        title -> Varchar,
        content -> Text,
        author_id -> Uuid,
        published -> Bool,
        created_at -> Timestamp,
        updated_at -> Timestamp,
    }
}

diesel::joinable!(posts -> users (author_id));

diesel::allow_tables_to_appear_in_same_query!(
    users,
    posts,
);

Model Definition and CRUD Operations

// src/models.rs
use chrono::{DateTime, Utc};
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::schema::{users, posts};

// User model (for queries)
#[derive(Queryable, Selectable, Serialize, Deserialize, Debug, Clone)]
#[diesel(table_name = users)]
pub struct User {
    pub id: Uuid,
    pub name: String,
    pub email: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

// For creating new users
#[derive(Insertable, Deserialize)]
#[diesel(table_name = users)]
pub struct NewUser {
    pub name: String,
    pub email: String,
}

// For updating users
#[derive(AsChangeset, Deserialize)]
#[diesel(table_name = users)]
pub struct UpdateUser {
    pub name: Option<String>,
    pub email: Option<String>,
    pub updated_at: DateTime<Utc>,
}

// Post model (for queries)
#[derive(Queryable, Selectable, Serialize, Deserialize, Debug, Clone)]
#[diesel(table_name = posts)]
pub struct Post {
    pub id: Uuid,
    pub title: String,
    pub content: String,
    pub author_id: Uuid,
    pub published: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

// For creating new posts
#[derive(Insertable, Deserialize)]
#[diesel(table_name = posts)]
pub struct NewPost {
    pub title: String,
    pub content: String,
    pub author_id: Uuid,
    pub published: Option<bool>,
}

// For updating posts
#[derive(AsChangeset, Deserialize)]
#[diesel(table_name = posts)]
pub struct UpdatePost {
    pub title: Option<String>,
    pub content: Option<String>,
    pub published: Option<bool>,
    pub updated_at: DateTime<Utc>,
}

// Join query result for post with author
#[derive(Queryable, Selectable, Serialize, Debug)]
pub struct PostWithAuthor {
    #[diesel(embed)]
    pub post: Post,
    #[diesel(embed)]
    pub author: User,
}

// CRUD operations implementation
impl User {
    // Get all users
    pub fn get_all(conn: &mut PgConnection) -> QueryResult<Vec<User>> {
        users::table
            .select(User::as_select())
            .order(users::created_at.desc())
            .load(conn)
    }

    // Get user by ID
    pub fn get_by_id(conn: &mut PgConnection, user_id: Uuid) -> QueryResult<Option<User>> {
        users::table
            .find(user_id)
            .select(User::as_select())
            .first(conn)
            .optional()
    }

    // Get user by email
    pub fn get_by_email(conn: &mut PgConnection, user_email: &str) -> QueryResult<Option<User>> {
        users::table
            .filter(users::email.eq(user_email))
            .select(User::as_select())
            .first(conn)
            .optional()
    }

    // Create new user
    pub fn create(conn: &mut PgConnection, new_user: NewUser) -> QueryResult<User> {
        diesel::insert_into(users::table)
            .values(&new_user)
            .returning(User::as_returning())
            .get_result(conn)
    }

    // Update user
    pub fn update(conn: &mut PgConnection, user_id: Uuid, update_user: UpdateUser) -> QueryResult<User> {
        diesel::update(users::table.find(user_id))
            .set(&update_user)
            .returning(User::as_returning())
            .get_result(conn)
    }

    // Delete user
    pub fn delete(conn: &mut PgConnection, user_id: Uuid) -> QueryResult<usize> {
        diesel::delete(users::table.find(user_id))
            .execute(conn)
    }

    // Get user's posts
    pub fn get_posts(&self, conn: &mut PgConnection) -> QueryResult<Vec<Post>> {
        Post::belonging_to(self)
            .select(Post::as_select())
            .order(posts::created_at.desc())
            .load(conn)
    }
}

impl Post {
    // Get all published posts
    pub fn get_published(conn: &mut PgConnection) -> QueryResult<Vec<Post>> {
        posts::table
            .filter(posts::published.eq(true))
            .select(Post::as_select())
            .order(posts::created_at.desc())
            .load(conn)
    }

    // Get posts with author information
    pub fn get_with_authors(conn: &mut PgConnection) -> QueryResult<Vec<PostWithAuthor>> {
        posts::table
            .inner_join(users::table)
            .filter(posts::published.eq(true))
            .select((Post::as_select(), User::as_select()))
            .order(posts::created_at.desc())
            .load::<(Post, User)>(conn)?
            .into_iter()
            .map(|(post, author)| Ok(PostWithAuthor { post, author }))
            .collect()
    }

    // Create new post
    pub fn create(conn: &mut PgConnection, new_post: NewPost) -> QueryResult<Post> {
        diesel::insert_into(posts::table)
            .values(&new_post)
            .returning(Post::as_returning())
            .get_result(conn)
    }

    // Update post
    pub fn update(conn: &mut PgConnection, post_id: Uuid, update_post: UpdatePost) -> QueryResult<Post> {
        diesel::update(posts::table.find(post_id))
            .set(&update_post)
            .returning(Post::as_returning())
            .get_result(conn)
    }
}

Complex Queries and Relationships

// src/queries.rs
use diesel::prelude::*;
use chrono::{DateTime, Utc};
use uuid::Uuid;

use crate::models::{User, Post, PostWithAuthor};
use crate::schema::{users, posts};

// Complex search query
pub fn search_posts_by_keyword(
    conn: &mut PgConnection,
    keyword: &str,
    limit: i64,
    offset: i64,
) -> QueryResult<Vec<PostWithAuthor>> {
    posts::table
        .inner_join(users::table)
        .filter(
            posts::title.ilike(format!("%{}%", keyword))
                .or(posts::content.ilike(format!("%{}%", keyword)))
        )
        .filter(posts::published.eq(true))
        .select((Post::as_select(), User::as_select()))
        .order(posts::created_at.desc())
        .limit(limit)
        .offset(offset)
        .load::<(Post, User)>(conn)?
        .into_iter()
        .map(|(post, author)| Ok(PostWithAuthor { post, author }))
        .collect()
}

// Complex query example using subqueries
pub fn get_recent_active_authors(
    conn: &mut PgConnection,
    days: i32,
) -> QueryResult<Vec<User>> {
    use diesel::dsl::*;
    
    let recent_date = Utc::now().naive_utc() - chrono::Duration::days(days as i64);
    
    // Subquery for recent authors
    let recent_authors = posts::table
        .filter(posts::created_at.gt(recent_date))
        .filter(posts::published.eq(true))
        .select(posts::author_id)
        .distinct();

    users::table
        .filter(users::id.eq_any(recent_authors))
        .select(User::as_select())
        .order(users::name.asc())
        .load(conn)
}

// Aggregation query example
#[derive(QueryableByName, Debug)]
pub struct UserPostCount {
    #[diesel(sql_type = diesel::sql_types::Uuid)]
    pub user_id: Uuid,
    #[diesel(sql_type = diesel::sql_types::Text)]
    pub user_name: String,
    #[diesel(sql_type = diesel::sql_types::BigInt)]
    pub post_count: i64,
}

pub fn get_user_post_counts(conn: &mut PgConnection) -> QueryResult<Vec<UserPostCount>> {
    diesel::sql_query(
        "
        SELECT 
            u.id as user_id,
            u.name as user_name,
            COUNT(p.id) as post_count
        FROM users u
        LEFT JOIN posts p ON u.id = p.author_id AND p.published = true
        GROUP BY u.id, u.name
        ORDER BY post_count DESC, u.name ASC
        "
    ).load(conn)
}

// Batch operations example
pub fn bulk_publish_posts(
    conn: &mut PgConnection,
    post_ids: Vec<Uuid>,
) -> QueryResult<usize> {
    diesel::update(
        posts::table.filter(posts::id.eq_any(post_ids))
    )
    .set((
        posts::published.eq(true),
        posts::updated_at.eq(Utc::now().naive_utc()),
    ))
    .execute(conn)
}

// Conditional deletion
pub fn delete_old_unpublished_posts(
    conn: &mut PgConnection,
    days: i32,
) -> QueryResult<usize> {
    let cutoff_date = Utc::now().naive_utc() - chrono::Duration::days(days as i64);
    
    diesel::delete(
        posts::table
            .filter(posts::published.eq(false))
            .filter(posts::created_at.lt(cutoff_date))
    )
    .execute(conn)
}

Async Support and Connection Pooling

// src/database.rs
use diesel_async::{AsyncPgConnection, AsyncConnection, pooled_connection::AsyncDieselConnectionManager};
use deadpool::managed::{Pool, Object};
use std::env;

pub type DbPool = Pool<AsyncDieselConnectionManager<AsyncPgConnection>>;
pub type DbConnection = Object<AsyncDieselConnectionManager<AsyncPgConnection>>;

// Database connection pool setup
pub async fn create_pool() -> DbPool {
    let database_url = env::var("DATABASE_URL")
        .expect("DATABASE_URL must be set");
    
    let manager = AsyncDieselConnectionManager::<AsyncPgConnection>::new(database_url);
    
    Pool::builder(manager)
        .max_size(16)
        .build()
        .expect("Failed to create connection pool")
}

// Async CRUD operations
impl User {
    pub async fn get_all_async(conn: &mut AsyncPgConnection) -> QueryResult<Vec<User>> {
        use crate::schema::users::dsl::*;
        
        users
            .select(User::as_select())
            .order(created_at.desc())
            .load(conn)
            .await
    }

    pub async fn create_async(conn: &mut AsyncPgConnection, new_user: NewUser) -> QueryResult<User> {
        use crate::schema::users::dsl::*;
        
        diesel::insert_into(users)
            .values(&new_user)
            .returning(User::as_returning())
            .get_result(conn)
            .await
    }

    pub async fn get_posts_async(&self, conn: &mut AsyncPgConnection) -> QueryResult<Vec<Post>> {
        use crate::schema::posts::dsl::*;
        
        Post::belonging_to(self)
            .select(Post::as_select())
            .order(created_at.desc())
            .load(conn)
            .await
    }
}

// Transaction processing
pub async fn create_user_with_post(
    pool: &DbPool,
    new_user: NewUser,
    new_post: NewPost,
) -> Result<(User, Post), Box<dyn std::error::Error>> {
    let mut conn = pool.get().await?;
    
    conn.transaction::<_, Box<dyn std::error::Error>, _>(|conn| {
        Box::pin(async move {
            // Create user
            let user = User::create_async(conn, new_user).await?;
            
            // Create post (set user ID)
            let mut post_data = new_post;
            post_data.author_id = user.id;
            let post = Post::create_async(conn, post_data).await?;
            
            Ok((user, post))
        })
    }).await
}

Testing and Mocking

// src/test_helpers.rs
#[cfg(test)]
pub mod test_helpers {
    use diesel::prelude::*;
    use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
    use diesel::pg::PgConnection;
    use std::env;

    pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");

    pub fn establish_test_connection() -> PgConnection {
        let database_url = env::var("TEST_DATABASE_URL")
            .expect("TEST_DATABASE_URL must be set for tests");
        
        let mut connection = PgConnection::establish(&database_url)
            .expect("Failed to connect to test database");
        
        // Run test migrations
        connection.run_pending_migrations(MIGRATIONS)
            .expect("Failed to run migrations");
        
        connection
    }

    pub fn cleanup_database(conn: &mut PgConnection) {
        use crate::schema::{posts, users};
        
        diesel::delete(posts::table).execute(conn).unwrap();
        diesel::delete(users::table).execute(conn).unwrap();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::models::*;
    use crate::test_helpers::*;

    #[test]
    fn test_user_crud() {
        let mut conn = establish_test_connection();
        cleanup_database(&mut conn);

        // User creation test
        let new_user = NewUser {
            name: "Test User".to_string(),
            email: "[email protected]".to_string(),
        };

        let created_user = User::create(&mut conn, new_user).unwrap();
        assert_eq!(created_user.name, "Test User");
        assert_eq!(created_user.email, "[email protected]");

        // User retrieval test
        let found_user = User::get_by_id(&mut conn, created_user.id).unwrap().unwrap();
        assert_eq!(found_user.id, created_user.id);

        // User update test
        let update_data = UpdateUser {
            name: Some("Updated User".to_string()),
            email: None,
            updated_at: chrono::Utc::now(),
        };

        let updated_user = User::update(&mut conn, created_user.id, update_data).unwrap();
        assert_eq!(updated_user.name, "Updated User");

        // User deletion test
        let deleted_count = User::delete(&mut conn, created_user.id).unwrap();
        assert_eq!(deleted_count, 1);

        let not_found = User::get_by_id(&mut conn, created_user.id).unwrap();
        assert!(not_found.is_none());
    }

    #[test]
    fn test_post_with_author() {
        let mut conn = establish_test_connection();
        cleanup_database(&mut conn);

        // Create test user
        let new_user = NewUser {
            name: "Author".to_string(),
            email: "[email protected]".to_string(),
        };
        let author = User::create(&mut conn, new_user).unwrap();

        // Create test post
        let new_post = NewPost {
            title: "Test Post".to_string(),
            content: "This is test post content.".to_string(),
            author_id: author.id,
            published: Some(true),
        };
        let post = Post::create(&mut conn, new_post).unwrap();

        // Test join query for post with author
        let posts_with_authors = Post::get_with_authors(&mut conn).unwrap();
        assert_eq!(posts_with_authors.len(), 1);
        assert_eq!(posts_with_authors[0].post.title, "Test Post");
        assert_eq!(posts_with_authors[0].author.name, "Author");
    }
}