Skip to content

SeaORM

Databases & CachingDatabase/ORMRust

What it is

SeaORM is an async, dynamic ORM for Rust with relations, a query builder, migrations and entity generation from an existing database.

Entities are generated from the database or written by hand. The query builder is composable at runtime, which suits filters assembled from user input.

Installation

cargo add sea-orm --features sqlx-postgres,runtime-tokio-rustls,macros

Getting started

The smallest useful thing you can do with it, and what each part means.

Entity and queries
#[derive(Clone, Debug, DeriveEntityModel)]
#[sea_orm(table_name = "books")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub title: String,
    pub year: i32,
    pub author_id: i32,
}

let books = Book::find()
    .filter(book::Column::Year.gte(1990))
    .order_by_desc(book::Column::Year)
    .limit(10)
    .all(db)
    .await?;

// Load a relation without an N+1 loop.
let with_authors = Book::find()
    .find_also_related(Author)
    .all(db)
    .await?;
find_also_related issues one join rather than a query per row — the equivalent of Prisma's include or GORM's Preload.

Advanced usage

Where the library earns its place over a simpler alternative.

Dynamic filters and transactions
// Build a query from optional user input — awkward in Diesel, natural here.
let mut query = Book::find();
if let Some(year) = params.year { query = query.filter(book::Column::Year.eq(year)); }
if let Some(q) = &params.search {
    query = query.filter(book::Column::Title.contains(q));
}
let results = query.paginate(db, 20).fetch_page(params.page).await?;

db.transaction::<_, (), DbErr>(|txn| Box::pin(async move {
    let book = book::ActiveModel { title: Set("Dune".to_owned()), ..Default::default() };
    book.insert(txn).await?;
    Ok(())
})).await?;
ActiveModel with Set marks which fields are being changed, so an update writes only the touched columns instead of the whole row.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

RecordNotFound
find_by_id returns Option; one_or_err style helpers return this error. Match on it rather than unwrapping.
Active model fields are all NotSet
Wrap values in Set(...). A field left as NotSet is omitted from the statement.

Best practices

  • Generate entities with sea-orm-cli from the real schema rather than hand-writing them.
  • Use ActiveModel and Set so updates touch only changed columns.
  • Use find_also_related or find_with_related instead of querying inside a loop.
  • Choose SeaORM when queries must be built dynamically; choose SQLx when the SQL is fixed and you want compile-time checks.

Background

Why it exists, and what it was reacting to.

SeaORM fills the gap between Diesel's compile-time rigidity and SQLx's raw SQL: it is async-first and supports dynamic query construction, which the other two make awkward.