Skip to main content

Announcing SeaORM 2.0 ๐Ÿš

ยท 8 min read
SeaQL Team
Chris Tsang
SeaORM 2.0 Banner

SeaORM 2.0 is now stable. It arrives after 43 release candidates, and it is the largest release in the project's history.

SeaORM was first introduced in 2021 as an async ORM for Rust, built on top of SeaQuery. 1.0 landed in 2024 and stabilized the core. 2.0 reshapes how you write entities and how you read and write related data, while keeping the 1.0 API available for a gradual migration.

versiondatenotes
0.1.02021-09-20initial release
1.0.02024-08-04first stable release
2.0.0-rc.12025-07-05first release candidate
2.0.02026-07-20this release

A community releaseโ€‹

2.0 was developed in the open, one release candidate at a time, with feedback and fixes from people running SeaORM in production. The numbers below are a snapshot at the time of writing.

GitHub stars~9,800
Downloads on crates.io22,000,000+
Releases published138
People who contributed to SeaORM278
Release candidates for 2.043
Issues closed900+
Pull requests merged885

SeaORM is one project in a wider ecosystem. The libraries it builds on and the tools built around it have grown alongside it:

projectwhat it isstars
SeaQuerythe query builder underneath SeaORM (now 1.0)1,700+
SeaographyGraphQL from your entities500+

Thank you to everyone who filed an issue, opened a pull request, answered a question, or sponsored the work. 2.0 is yours as much as ours.

What changed in 2.0โ€‹

A short tour of the headline features. Each links to a dedicated post if you want the details.

A new entity formatโ€‹

The 1.0 entity format is explicit but verbose. 2.0 adds a denser format where relations live on the Model as typed fields, generated with sea-orm-cli generate entity --entity-format dense.

user.rs
#[sea_orm::model]
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "user")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
#[sea_orm(unique)]
pub email: String,
#[sea_orm(has_one)]
pub profile: HasOne<super::profile::Entity>,
#[sea_orm(has_many)]
pub posts: HasMany<super::post::Entity>,
}

The inverse side is declared with belongs_to. The field type now pairs with BelongsTo, which encodes the foreign key's cardinality in the type: BelongsTo<Entity> for a non-null key, BelongsTo<Option<Entity>> for a nullable one.

post.rs
#[sea_orm::model]
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "post")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub user_id: i32,
pub title: String,
#[sea_orm(belongs_to, from = "user_id", to = "id")]
pub author: BelongsTo<super::user::Entity>,
#[sea_orm(has_many)]
pub comments: HasMany<super::comment::Entity>,
}

Read the new entity format post, or the full walk-through that builds a small blogging platform end to end.

Entity-first workflowโ€‹

Hand-write your entities and let SeaORM create the tables and foreign keys in the correct order. Call sync after connecting, and the schema converges to match your entities.

db.get_schema_registry("my_crate::entity::*").sync(db).await?;

See entity-first workflow.

Nested ActiveModelโ€‹

Build a nested object and save it in one call. SeaORM walks the tree, works out what changed, and executes the inserts and updates in one transaction, in foreign-key order.

user::ActiveModel::builder()
.set_name("Bob")
.set_profile(profile::ActiveModel::builder().set_picture("tennis.jpg"))
.add_post(post::ActiveModel::builder().set_title("A sunny day"))
.save(db)
.await?;

See nested ActiveModel.

Strongly-typed columnsโ€‹

Columns are now available as a COLUMN constant with type-aware methods, so a mismatched value is a compile error instead of a runtime surprise โ€” and you can drop the CamelCase.

// old: an enum variant
user::Entity::find().filter(user::Column::Name.contains("Bob"));

// new: a typed constant
user::Entity::find().filter(user::COLUMN.name.contains("Bob"));

See strongly-typed columns.

More in 2.0โ€‹

Upgrading from 1.0โ€‹

The 1.0 API still works in 2.0, so you can upgrade incrementally. The main change to be aware of is the entity format: a belongs_to field now pairs with BelongsTo<Entity> (or BelongsTo<Option<Entity>>) rather than the older HasOne / Option types, which remain supported as a legacy path.

The SeaORM 2.0 Migration Guide walks through the upgrade step by step.

Visionโ€‹

Our aim is simple to state and hard to earn: we want SeaORM to be the data layer a Rust developer reaches for by default, the Active Record of Rust. If you are coming from a dynamic language, SeaORM should feel right at home: the ergonomics you are used to, backed by the strong typing and performance of Rust.

That aim is not new. SeaORM has followed the Active Record pattern from the start, and ActiveModel is right there in the name. The question driving the project is whether Rust can have that productivity without giving up the guarantees that make it worth using. 2.0 is our clearest answer yet, and there is still a long way to go. We are glad to be walking it with you.

๐ŸŒŸ Sponsorsโ€‹

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our GitHub sponsors:


๐Ÿฆ€ Rustacean Sticker Packโ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. Our stickers are made with a premium water-resistant vinyl with a unique matte finish.

Sticker Pack Contents:

  • Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography
  • Mascots: Ferris the Crab x 3, Terres the Hermit Crab
  • The Rustacean wordmark

Support SeaQL and get a Sticker Pack!

Rustacean Sticker Pack by SeaQL