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,macrosGetting started
The smallest useful thing you can do with it, and what each part means.
rust
#[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?;Advanced usage
Where the library earns its place over a simpler alternative.
rust
// 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) = ¶ms.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?;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.
