ORM with DSL and schema.rs generated. In Axum/Tokio: diesel-async + pool (deadpool-diesel / bb8 of the project). Not diesel::Connection synchronous in the handler — this blocks the runtime (tokio).
Explicit SQL: sqlx. Async ORM without Diesel DSL: sea-orm. Do not introduce Diesel if the crate is already sqlx/SeaORM.
Stack
| Ask | Role |
|---|---|
diesel |
DSL, table!, migrations CLI |
diesel_migrations |
embed in the binary if the repo already embeds |
diesel-async |
AsyncPgConnection / run_query |
| Pool | deadpool / bb8 — the project's Cargo.toml |
CLI (diesel setup, migration, print-schema) in the team's flow. schema.rs is generated — do not edit by hand unless the project already does so.
Organization
migrations/
src/
schema.rs # gerado
models/
db.rs # pool async
- Models (
Queryable,Insertable,AsChangeset) near the domain. - Features
postgres/chrono/uuidaligned with the schema.
Non-negotiable rules
- Axum handler:
.awaitindiesel_async::RunQueryDsl. Sync only inspawn_blockingif the repo still has not migrated — do not add new sync. diesel::update/filterwithidand tenancy. Unique →AppError409.- Async transaction (
conn.transaction) in composite writes. print-schema/ migration in the same PR that changed the table.- Pool in
State. No opening a connection per request.
Query DSL
users::table.filter(users::id.eq(id)).first()— types fromschema.rs.- Explicit Select in large listings (
select) — avoid loading the entire row if you only need 3 columns. for_updatein the mutated aggregate;skip_lockedin worker (background-jobs).- Deep offset: prefer keyset (
postgres).
Do not build SQL strings if the DSL covers it. sql_query only in a justified corner (FTS, specific upsert).
Axum
async fn get_user(
State(pool): State<Pool>,
Path(id): Path<i32>,
) -> Result<Json<User>, AppError> {
let mut conn = pool.get().await?;
let user = users::table.find(id).first(&mut conn).await?;
Ok(Json(user))
}
- Map
NotFound/DatabaseErrorunique. No diesel internals in the body. - SQLite in CLI/sync test may remain; HTTP API stays async.
Anti-patterns
establish_connection+block_onin the extractor- Edit
schema.rsand forget the migration load::<T>of the entire table- Diesel + SeaORM in the same service without a crate boundary
expect("db")in production
Integration with other skills
- Runtime:
tokio(spawn_blockingonly for legacy sync) - HTTP:
axum - Alternatives:
sqlx,sea-orm - Jobs:
background-jobs - Tests:
rust-unit-testing
Completion criteria
- Async queries on the HTTP path (or documented legacy explicit blocking)
schema.rs+ coherent migration- Pool in state; composite tx
- Tenancy in
filter - Mapped errors; no DSL leaked in the API