> ## Documentation Index
> Fetch the complete documentation index at: https://nestcrate.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# nestrs-sea-orm

> SeaORM Repo, Bind, ambient transactions, expose schemas, and deny-closed row authz — NestJS TypeORM / NestRS data-layer analogue.

TypeORM and Sequelize are Node ORMs. This crate binds [SeaORM](https://www.sea-ql.org/SeaORM/) into the nestrs DI graph and adds the data-layer pieces Nest / NestRS teams expect:

| Capability | API |
| - | - |
| Connect + inject | `SeaOrmModule::for_root_async` / `from_connection` |
| Typed repository | `Repo<E>` (ambient-tx aware) |
| Request transactions | `install_sea_orm_transactional_middleware` |
| Deny-closed authz | `RowAuthz`, `AbilityAuthz`, `*_authorized` |
| Path → authorized row | `bind_read` / `Bind::read` (NestRS `Bind` analogue) |
| One type → OpenAPI | `expose_schema` (feature `expose`) |

Drizzle stays carved out of workspace members. For Prisma-style access use [`nestrs-prisma`](/ecosystem/database); for raw SQL use `SqlxDatabaseModule`.

## Install

```toml theme={null}
[dependencies]
nestrs = { version = "1.6.0", features = ["sea-orm-authz", "sea-orm-expose", "openapi"] }
# or the adapter directly:
nestrs-sea-orm = { version = "1.6.0", features = ["expose"] }
```

This crate enables SeaORM's `sqlx-sqlite` feature (`sqlite::memory:` and file URLs work out of the box). For Postgres or MySQL, add a direct `sea-orm` dependency with the matching driver feature (same 1.1 line) in your app.

## Connect and inject

`for_root_async` must finish **before** `NestFactory::create`. Merge the `DynamicModule` with `create_with_modules` so `Arc<DatabaseConnection>` is exported:

```rust theme={null}
use nestrs::prelude::*;
use nestrs_sea_orm::{Repo, SeaOrmModule};
use sea_orm::DatabaseConnection;
use std::sync::Arc;

#[injectable]
struct PostsService {
    db: Arc<DatabaseConnection>,
}

impl PostsService {
    fn repo(&self) -> Repo<post::Entity> {
        Repo::new(self.db.clone())
    }
}
```

## Ambient transactions

Apply `install_sea_orm_transactional_middleware` with `from_fn_with_state(db, …)`. Inside the request, `Repo` methods prefer the ambient transaction. **2xx / 3xx / 4xx → commit**, **5xx → rollback**.

```rust theme={null}
use axum::middleware::from_fn_with_state;
use nestrs_sea_orm::install_sea_orm_transactional_middleware;

let router = router.layer(from_fn_with_state(
    db.clone(),
    install_sea_orm_transactional_middleware,
));
```

## Bind — authorized path → row

NestRS hands you `Bind<Read, UsersService>` as a handler parameter. In nestrs, load once with deny-closed policy:

```rust theme={null}
use axum::extract::{Path, State};
use axum::Json;
use nestrs::current_ability_authz;
use nestrs_sea_orm::{bind_read, BindError, Repo};
use sea_orm::DatabaseConnection;
use std::sync::Arc;

#[get("/posts/:id")]
#[use_guards(AuthnGuard, PoliciesGuard)]
async fn show(
    State(db): State<Arc<DatabaseConnection>>,
    Path(id): Path<i32>,
) -> Result<Json<post::Model>, BindError> {
    let repo = Repo::<post::Entity>::new(db);
    let authz = current_ability_authz().ok_or(BindError::MissingAuthz)?;
    let model = bind_read(&repo, &authz, "Post", id).await?;
    Ok(Json(model))
}
```

`BindError` maps to **401** (missing authz), **403** (denied), **404** (missing / invisible), **500** (db).

Attach ability onto request extensions when you build custom extractors:

```rust theme={null}
use axum::middleware::from_fn;
use nestrs::attach_row_authz_middleware;

// Last `.layer` is outermost — policies should wrap attach.
router
    .layer(from_fn(attach_row_authz_middleware))
    .layer(policies_layer);
```

## Expose — one model type for OpenAPI

Derive `schemars::JsonSchema` on the entity `Model` (or a view DTO) and register it once:

```rust theme={null}
use nestrs_openapi::OpenApiOptions;
use nestrs_sea_orm::expose_schema;

let opts = OpenApiOptions::default()
    .with_schemas([expose_schema::<post::Model>("Post")])
    .with_posture_security(true); // lock icons for #[use_guards] routes
```

Reuse the same type in GraphQL `SimpleObject` / resolvers so OpenAPI and GraphQL cannot drift.

## Row-level authz

```rust theme={null}
use nestrs::current_ability_authz;

let authz = current_ability_authz().expect("policies middleware");
let row = repo
    .find_by_id_authorized(&authz, "Post", id)
    .await?;
```

Authorized helpers are **deny-closed**: missing ability / failed predicate → `RepoError::Denied` (or `Ok(None)` for invisible reads).

## Route posture (umbrella)

```rust theme={null}
#[get("/health")]
#[public]
async fn health() -> &'static str { "ok" }

#[get("/posts/:id")]
#[use_guards(AuthnGuard, PoliciesGuard)]
async fn show(...) { ... }

NestFactory::create::<AppModule>()
    .require_route_posture()
    .listen_graceful(3000)
    .await;
```

## GraphQL DataLoader + SeaORM

Register a per-request loader that batches through `Repo` (sees the ambient tx when the HTTP middleware is installed):

```rust theme={null}
use nestrs_graphql::data_loader;
// inside batch_load: one SELECT ... WHERE id IN (...) via Repo / SeaORM QueryFilter
```

## Compared with other SQL paths

| Path | When to use |
| - | - |
| `database-sqlx` / `SqlxDatabaseModule` | Raw SQL, `AnyPool`, `CrudService` |
| `nestrs-prisma` | Schema-driven repositories, `prisma_model!` |
| **`nestrs-sea-orm` (recommended ORM)** | SeaORM entities, `Repo`, `Bind`, ambient tx, expose |
| `nestrs-drizzle` | Not a workspace member in 1.6.0 |
