SeaORM

SeaORM is "an async & dynamic ORM for Rust" developed as a modern ORM (Object-Relational Mapping) library gaining attention in the contemporary Rust ecosystem. It provides native async processing support and powerful type safety for PostgreSQL, MySQL, and SQLite. Developed by the SeaQL team, it implements a Functional Relational Mapping (FRM) approach that achieves compile-time type checking and excellent performance. Integration with major web frameworks like Axum and Actix-web is straightforward, and schema-driven development with code generation significantly improves Rust productivity.

ORMRustAsyncEntityMigrationType Safety

GitHub Overview

SeaQL/sea-orm

🐚 An async & dynamic ORM for Rust

Stars8,582
Watchers39
Forks600
Created:February 9, 2021
Language:Rust
License:Apache License 2.0

Topics

databasehacktoberfestlocomariadbmysqlormpostgresrustsqlsqlitesqlxtokio

Star History

SeaQL/sea-orm Star History
Data as of: 8/13/2025, 01:43 AM

Library

SeaORM

Overview

SeaORM is "an async & dynamic ORM for Rust" developed as a modern ORM (Object-Relational Mapping) library gaining attention in the contemporary Rust ecosystem. It provides native async processing support and powerful type safety for PostgreSQL, MySQL, and SQLite. Developed by the SeaQL team, it implements a Functional Relational Mapping (FRM) approach that achieves compile-time type checking and excellent performance. Integration with major web frameworks like Axum and Actix-web is straightforward, and schema-driven development with code generation significantly improves Rust productivity.

Details

SeaORM 2025 edition provides the best combination of Rust's async programming and database access as a mature ORM library. Full support for async/await syntax enables complete integration with async runtimes like Tokio and async-std, achieving high throughput. Entity definitions use the DeriveEntityModel macro, supporting automatic conversion from database schemas to Rust structs. Migration functionality through sea-orm-migration enables safe and incremental management of database schema evolution.

Key Features

  • Full Async Support: High-performance async processing with Tokio and async-std
  • Strong Type Safety: Compile-time error detection and auto-completion
  • Multi-Database Support: Supports PostgreSQL, MySQL, and SQLite
  • Schema-Driven Development: Entity code generation from database schemas
  • Advanced Query Building: Type-safe query builder and relation operations
  • Migration Management: Version-controlled schema changes

Pros and Cons

Pros

  • High safety and performance through complete integration with Rust's ownership system
  • High throughput achieved through native async processing support
  • Compile-time error detection and bug reduction through powerful type system
  • Flexible choice according to project requirements with multi-database support
  • Comprehensive documentation and active community support
  • Automated code generation and migration management through CLI tools

Cons

  • High learning cost for Rust, creating barriers for beginners
  • Complex queries require fallback to raw SQL
  • Limited library choices compared to other language ecosystems like Node.js
  • Error messages during debugging can become complex
  • Few integration examples with legacy systems due to being a new library
  • Compilation time can become long (especially for large projects)

Reference Pages

Code Examples

Setup and Project Initialization

# Add dependencies to Cargo.toml
cargo add sea-orm
cargo add sea-orm-cli --dev

# Enable PostgreSQL features
cargo add sea-orm --features "sqlx-postgres,runtime-tokio-rustls,macros"

# For migrations
cargo add sea-orm-migration

# Install CLI tool
cargo install sea-orm-cli

Basic Entity Definition

use sea_orm::entity::prelude::*;

// User entity definition
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "users")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub name: String,
    pub email: String,
    pub created_at: DateTimeUtc,
    pub updated_at: Option<DateTimeUtc>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
    #[sea_orm(has_many = "super::post::Entity")]
    Posts,
}

impl Related<super::post::Entity> for Entity {
    fn to() -> RelationDef {
        Relation::Posts.def()
    }
}

impl ActiveModelBehavior for ActiveModel {}

// Post entity definition
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "posts")]
pub struct PostModel {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub user_id: i32,
    pub title: String,
    pub content: String,
    pub published: bool,
    pub created_at: DateTimeUtc,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum PostRelation {
    #[sea_orm(
        belongs_to = "super::user::Entity",
        from = "Column::UserId",
        to = "super::user::Column::Id"
    )]
    User,
}

impl Related<super::user::Entity> for super::post::Entity {
    fn to() -> RelationDef {
        PostRelation::User.def()
    }
}

Database Connection and Query Operations

use sea_orm::*;

// Establish database connection
#[tokio::main]
async fn main() -> Result<(), DbErr> {
    // PostgreSQL connection
    let db = Database::connect("postgresql://user:password@localhost/database").await?;
    
    // Connection options configuration
    let mut opt = ConnectOptions::new("postgresql://user:password@localhost/database");
    opt.max_connections(100)
        .min_connections(5)
        .connect_timeout(Duration::from_secs(8))
        .idle_timeout(Duration::from_secs(8));
    
    let db = Database::connect(opt).await?;

    // Fetch all users
    let users: Vec<user::Model> = User::find().all(&db).await?;
    println!("Found {} users", users.len());

    // Conditional filtering
    let active_users: Vec<user::Model> = User::find()
        .filter(user::Column::Email.contains("@example.com"))
        .all(&db)
        .await?;

    // Fetch single record
    let user: Option<user::Model> = User::find_by_id(1).one(&db).await?;
    if let Some(user) = user {
        println!("User: {}", user.name);
    }

    // Pagination
    let paginator = User::find()
        .order_by_asc(user::Column::Id)
        .paginate(&db, 10);
    
    let users_page_1 = paginator.fetch_page(0).await?;
    let total_pages = paginator.num_pages().await?;

    Ok(())
}

Record Creation and Updates

use sea_orm::*;
use chrono::Utc;

async fn create_and_update_users(db: &DatabaseConnection) -> Result<(), DbErr> {
    // Create new user
    let new_user = user::ActiveModel {
        name: Set("John Doe".to_owned()),
        email: Set("[email protected]".to_owned()),
        created_at: Set(Utc::now()),
        ..Default::default()
    };

    let user: user::Model = new_user.insert(db).await?;
    println!("Created user with ID: {}", user.id);

    // Bulk insert multiple records
    let users_data = vec![
        user::ActiveModel {
            name: Set("Jane Smith".to_owned()),
            email: Set("[email protected]".to_owned()),
            created_at: Set(Utc::now()),
            ..Default::default()
        },
        user::ActiveModel {
            name: Set("Bob Johnson".to_owned()),
            email: Set("[email protected]".to_owned()),
            created_at: Set(Utc::now()),
            ..Default::default()
        },
    ];

    let insert_result = User::insert_many(users_data).exec(db).await?;
    println!("Inserted {} users", insert_result.rows_affected);

    // Update record
    let user_to_update: user::ActiveModel = User::find_by_id(1)
        .one(db)
        .await?
        .unwrap()
        .into();

    let mut user_to_update = user_to_update;
    user_to_update.name = Set("John Doe (Updated)".to_owned());
    user_to_update.updated_at = Set(Some(Utc::now()));

    let updated_user: user::Model = user_to_update.update(db).await?;
    println!("Updated user: {}", updated_user.name);

    // Bulk update with conditions
    User::update_many()
        .col_expr(user::Column::UpdatedAt, Expr::value(Utc::now()))
        .filter(user::Column::CreatedAt.lt(Utc::now() - Duration::days(30)))
        .exec(db)
        .await?;

    Ok(())
}

Relation Operations and Join Queries

use sea_orm::*;

async fn work_with_relations(db: &DatabaseConnection) -> Result<(), DbErr> {
    // Lazy loading
    let user = User::find_by_id(1).one(db).await?.unwrap();
    let posts: Vec<post::Model> = user.find_related(Post).all(db).await?;
    println!("User {} has {} posts", user.name, posts.len());

    // Eager loading
    let users_with_posts: Vec<(user::Model, Vec<post::Model>)> = User::find()
        .find_with_related(Post)
        .all(db)
        .await?;

    for (user, posts) in users_with_posts {
        println!("User: {}, Posts: {}", user.name, posts.len());
    }

    // Custom joins
    let users_with_published_posts = User::find()
        .join(JoinType::InnerJoin, user::Relation::Posts.def())
        .filter(post::Column::Published.eq(true))
        .group_by(user::Column::Id)
        .all(db)
        .await?;

    // Complex queries and subqueries
    let active_users = User::find()
        .filter(
            user::Column::Id.in_subquery(
                Query::select()
                    .column(post::Column::UserId)
                    .from(post::Entity)
                    .and_where(post::Column::CreatedAt.gte(Utc::now() - Duration::days(7)))
                    .to_owned()
            )
        )
        .all(db)
        .await?;

    // Using aggregate functions
    let post_count: Option<i64> = Post::find()
        .filter(post::Column::Published.eq(true))
        .count(db)
        .await
        .ok();

    println!("Published posts count: {:?}", post_count);

    Ok(())
}

Migration Management

use sea_orm_migration::prelude::*;

// Migration definition
#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .create_table(
                Table::create()
                    .table(User::Table)
                    .if_not_exists()
                    .col(
                        ColumnDef::new(User::Id)
                            .integer()
                            .not_null()
                            .auto_increment()
                            .primary_key(),
                    )
                    .col(ColumnDef::new(User::Name).string().not_null())
                    .col(
                        ColumnDef::new(User::Email)
                            .string()
                            .not_null()
                            .unique_key(),
                    )
                    .col(
                        ColumnDef::new(User::CreatedAt)
                            .timestamp()
                            .not_null(),
                    )
                    .col(ColumnDef::new(User::UpdatedAt).timestamp())
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .drop_table(Table::drop().table(User::Table).to_owned())
            .await
    }
}

#[derive(Iden)]
enum User {
    Table,
    Id,
    Name,
    Email,
    CreatedAt,
    UpdatedAt,
}

// Execute migration
#[tokio::main]
async fn main() -> Result<(), DbErr> {
    let db = Database::connect("postgresql://user:password@localhost/database").await?;
    
    let schema_manager = SchemaManager::new(&db);
    Migration.up(&schema_manager).await?;
    
    println!("Migration applied successfully!");
    Ok(())
}

Error Handling and Transactions

use sea_orm::*;

async fn transaction_example(db: &DatabaseConnection) -> Result<(), DbErr> {
    // Transaction processing
    let txn = db.begin().await?;

    let user_result = user::ActiveModel {
        name: Set("Transaction User".to_owned()),
        email: Set("[email protected]".to_owned()),
        created_at: Set(Utc::now()),
        ..Default::default()
    }.insert(&txn).await;

    match user_result {
        Ok(user) => {
            let post_result = post::ActiveModel {
                user_id: Set(user.id),
                title: Set("First Post".to_owned()),
                content: Set("Post created within transaction".to_owned()),
                published: Set(true),
                created_at: Set(Utc::now()),
                ..Default::default()
            }.insert(&txn).await;

            match post_result {
                Ok(_) => {
                    txn.commit().await?;
                    println!("Transaction committed successfully");
                }
                Err(e) => {
                    txn.rollback().await?;
                    println!("Post creation failed, transaction rolled back: {}", e);
                }
            }
        }
        Err(e) => {
            txn.rollback().await?;
            println!("User creation failed, transaction rolled back: {}", e);
        }
    }

    // Functional transaction
    db.transaction::<_, (), DbErr>(|txn| {
        Box::pin(async move {
            let user = user::ActiveModel {
                name: Set("Functional User".to_owned()),
                email: Set("[email protected]".to_owned()),
                created_at: Set(Utc::now()),
                ..Default::default()
            }.insert(txn).await?;

            post::ActiveModel {
                user_id: Set(user.id),
                title: Set("Functional Post".to_owned()),
                content: Set("Created within functional transaction".to_owned()),
                published: Set(true),
                created_at: Set(Utc::now()),
                ..Default::default()
            }.insert(txn).await?;

            Ok(())
        })
    }).await?;

    // Error handling patterns
    match User::find_by_id(999).one(db).await {
        Ok(Some(user)) => println!("Found user: {}", user.name),
        Ok(None) => println!("User not found"),
        Err(DbErr::ConnectionAcquire(e)) => {
            eprintln!("Database connection error: {}", e);
        }
        Err(DbErr::Query(RuntimeErr::SqlxError(e))) => {
            eprintln!("SQL execution error: {}", e);
        }
        Err(e) => {
            eprintln!("Other database error: {}", e);
        }
    }

    Ok(())
}

Performance Optimization

use sea_orm::*;

async fn performance_optimization(db: &DatabaseConnection) -> Result<(), DbErr> {
    // Batch operations
    let batch_size = 1000;
    let mut users_batch = Vec::with_capacity(batch_size);
    
    for i in 0..batch_size {
        users_batch.push(user::ActiveModel {
            name: Set(format!("Batch User {}", i)),
            email: Set(format!("batch{}@example.com", i)),
            created_at: Set(Utc::now()),
            ..Default::default()
        });
    }

    User::insert_many(users_batch).exec(db).await?;

    // Partial field selection
    let user_names: Vec<String> = User::find()
        .select_only()
        .column(user::Column::Name)
        .into_tuple()
        .all(db)
        .await?;

    // Index usage optimization
    let users = User::find()
        .filter(user::Column::Email.starts_with("admin"))
        .order_by_asc(user::Column::CreatedAt)
        .limit(100)
        .all(db)
        .await?;

    // Streaming queries (large data processing)
    let mut stream = User::find().stream(db).await?;
    while let Some(user) = stream.try_next().await? {
        // Process large data one record at a time
        process_user(&user).await;
    }

    // Raw SQL usage (complex queries)
    let custom_results: Vec<serde_json::Value> = db
        .query_all(Statement::from_string(
            DatabaseBackend::Postgres,
            r#"
            SELECT u.name, COUNT(p.id) as post_count
            FROM users u
            LEFT JOIN posts p ON u.id = p.user_id
            WHERE u.created_at > $1
            GROUP BY u.id, u.name
            ORDER BY post_count DESC
            LIMIT 10
            "#.to_owned(),
        ).values(vec![
            (Utc::now() - Duration::days(30)).into()
        ]))
        .await?
        .into_iter()
        .map(|row| row.try_get_by_index(0).unwrap())
        .collect();

    Ok(())
}

async fn process_user(user: &user::Model) {
    // User processing logic
    tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
}