Open-source project
SeaQL/sea-orm avatar
SeaQL/sea-orm

SeaORM 2.0: Dense Entities, a Three-Query Loader and a TLS Switch You Set Yourself

🐚 A powerful relational ORM for Rust

9,968 stars749 forksRustApache-2.0

At a glance

What is it?
SeaORM is an async ORM for Rust services on top of SQLx. This piece covers what the 2.0 entity format changes, what the loader and nested saves require, and where you have to make choices yourself.
Who is it for?
SeaORM suits Rust teams building REST, GraphQL or gRPC services who are willing to adopt the 2.0 dense entity format and pick their own SQLx TLS backend. It is a poor fit for a project pinned to an older toolchain, since 2.0.4 declares rust-version 1.94.0, and for anyone who wants nested saves without regenerating entities.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • SeaQL/sea-orm9,968 ★ · +63
Star history
Repository2026-09-222026-11-14
SeaQL/sea-orm9.9k10k

63 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Default features leave TLS off, and SQLx decides the backend

A fresh sea-orm dependency does not enable any TLS. The README says SeaORM's default features do not enable TLS, and that to select a TLS backend for SQLx you add a SQLx dependency with the TLS feature you want. SeaORM itself only forwards the database and runtime choice. The example pairs sea-orm with sqlx-postgres and runtime-tokio, then adds a separate sqlx entry for AWS-LC:

toml
[dependencies]
sea-orm = { version = "2", features = ["sqlx-postgres", "runtime-tokio"] }
sqlx = { version = "0.9", features = ["tls-rustls-aws-lc-rs"] }

The consequence is that two version numbers have to agree: SeaORM at 2 and SQLx at 0.9. If you copy only the first line and your database host demands an encrypted connection, the missing TLS feature is your problem to find, because nothing in the default feature set supplies it. Treat the sqlx line as part of the install, not an optional extra.

sea-orm-cli ships with a native-tls default you must switch off

The same choice appears again when sea-orm-cli is used as a dependency instead of a binary. Its defaults enable runtime-tokio-native-tls, so picking a different TLS backend means turning the defaults off and listing the features by hand:

toml
[dependencies]
sea-orm-cli = { version = "2", default-features = false, features = [
    "codegen",
    "sqlx-postgres",
    "runtime-tokio",
] }
sqlx = { version = "0.9", features = ["tls-rustls-aws-lc-rs"] }

Notice the asymmetry. The library crate starts with no TLS at all, while the CLI crate starts with native-tls. Leaving default-features on and adding a rustls feature next to it would give you two backends in one dependency tree. The sea-orm-cli crate has its own releases too: [email protected] was published on 2026-09-27, minutes after the 2.0.4 library tag, so the two are versioned together.

The dense entity format is generated, and nested saves depend on it

SeaORM 2.0 introduces a dense entity format. You are not expected to type it: entity files are generated from an existing database with sea-orm-cli using the --entity-format dense option, which the README marks as new in 2.0. The generated Model carries relations as fields next to plain columns, for example a profile field of type HasOne and a posts field of type HasMany, together with the sea_orm::model attribute macro.

This is not cosmetic. The nested ActiveModel builder, which can persist a user, a one-to-one profile, one-to-many posts and many-to-many tags in one operation, requires the dense format. Entities generated in some other layout will not give you that builder. If you already have a codebase of older entity files, adopting nested persistence means regenerating them, and any hand edits to those files have to be carried across.

The Entity Loader issues three queries for a nested user

The loader is SeaORM's answer to the N+1 problem. It joins for one-to-one relations and uses a data loader for one-to-many ones. The README example loads user 42 with a profile, posts and tags, and spells out what runs under the hood:

rust
// 3 queries are executed under the hood:
// 1. SELECT FROM user JOIN profile WHERE id = $
// 2. SELECT FROM post WHERE user_id IN (..)
// 3. SELECT FROM tag JOIN post_tag WHERE post_id IN (..)

The query count is fixed by the shape of the relation tree, not by how many posts the user has. That is the point of the data loader: posts are fetched with an IN list and tags through the post_tag join table, so ten posts and ten thousand posts cost the same three round trips. The flip side is that you choose join versus loader per relation by the kind of relation, not by measurement. The result comes back as a ModelEx value holding the nested structure, and the README does not claim any control over which strategy is used for a given relation.

Entity-first schema sync needs two feature flags and a module path

SeaORM keeps the migration system for creating tables, modifying schemas and seeding data, and 2.0 adds an Entity First Workflow on top. You define new entities or add columns, and SeaORM detects the changes and creates the new tables, columns, unique keys and foreign keys. It resolves foreign key dependencies and creates tables in topological order. The call is one line:

rust
// SeaORM resolves foreign key dependencies and creates the tables in topological order.
// Requires the `entity-registry` and `schema-sync` feature flags.
db.get_schema_registry("my_crate::entity::*").sync(db).await;

Two things limit it. The entity-registry and schema-sync flags are not on by default, so the line will not compile until you enable them. And the argument is a module path pattern, so entities living outside my_crate::entity are not seen unless you point the registry at them. The README describes creation of tables, columns and keys; it does not describe dropping or renaming, so do not assume a rename is carried through as a rename.

raw_sql! exists for the queries the builder cannot express

The README's own estimate is that SeaORM handles 95% of transactional queries and leaves the rest to raw SQL. The escape hatch is the raw_sql! macro passed to from_raw_sql. It takes the backend as its first argument, here Sqlite, followed by the SQL string. Placeholders are written as {user.name} for nested parameter access, and {..ids} expands an array into the IN list:

rust
let user: Option<user::Model> = user::Entity::find()
    .from_raw_sql(raw_sql!(
        Sqlite,
        r#"SELECT "id", "name" FROM "user"
           WHERE "name" LIKE {user.name}
           AND "id" in ({..ids})
        "#
    ))
    .one(db)
    .await?;

The result still lands in a typed user::Model, so you keep the entity mapping while writing your own SELECT. What you give up is the portability the builder provides: the backend is named in the macro call, and the column list is yours to keep in step with the entity.

Version 2.0.4 pins Rust 1.94.0 and edition 2024

The sea-orm crate at version 2.0.4 declares rust-version 1.94.0 and edition 2024 in its Cargo.toml. A project on an older compiler cannot take this release. The workspace members are the root crate, sea-orm-macros, sea-orm-codegen and sea-orm-arrow, while sea-orm-cli, sea-orm-migration, sea-orm-rocket and sea-orm-sync sit as separate directories in the repository.

The docs.rs build enables a long feature list including mock, proxy, rbac, schema-sync, tracing-spans, postgres-array, postgres-vector, with-ipnetwork and with-arrow, which tells you the optional surface is wide and that you should enable only what you use. Licensing needs a look too: GitHub reports Apache-2.0, the Cargo manifest says MIT OR Apache-2.0, and the repository contains both LICENSE-APACHE and LICENSE-MIT.

On upkeep, the last push was on 2026-10-30, and the recent releases are 2.0.4 on 2026-09-27 and 2.0.3 on 2026-09-13. The repository is not archived. Examples cover Actix, Axum, GraphQL, jsonrpsee, Loco, Poem, Rocket, Salvo, Tonic and Seaography, plus a single-file quickstart.

Editorial conclusion

SeaORM suits Rust teams building REST, GraphQL or gRPC services who are willing to adopt the 2.0 dense entity format and pick their own SQLx TLS backend. It is a poor fit for a project pinned to an older toolchain, since 2.0.4 declares rust-version 1.94.0, and for anyone who wants nested saves without regenerating entities. Before committing, check which TLS feature your database host requires and whether the entity-first schema sync fits your migration process.

Frequently asked questions

What is SeaORM?

SeaORM is an async ORM for building web services in Rust, with relations, filters, pagination and nested queries aimed at REST, GraphQL and gRPC APIs. It is licensed Apache-2.0 on GitHub and its Cargo manifest says MIT OR Apache-2.0.

Is there a PostgreSQL ORM written in Rust?

SeaORM supports PostgreSQL through the sqlx-postgres feature, for example with runtime-tokio. Its Cargo keywords list mysql, postgres and sqlite.

How does SeaORM relate to SQLx?

SeaORM uses SQLx as its database backend through features such as sqlx-postgres. TLS is not enabled by default, so you add a sqlx dependency with the TLS feature you want, for example tls-rustls-aws-lc-rs on sqlx 0.9.

Which is better, raw SQL or an ORM, when using SeaORM?

SeaORM says it can handle 95% of transactional queries and offers the raw_sql! macro with from_raw_sql for the remaining cases that are too complex to express.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. SeaQL/sea-orm on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/seaql-sea-orm.svg)](https://hysenlabs.com/projects/seaql-sea-orm)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/seaql-sea-orm.svg)](https://hysenlabs.com/projects/seaql-sea-orm)

Was this useful?