It also names the honest alternatives — sea-orm for an ActiveRecord-style ORM, diesel for a
mature but synchronous alternative needing spawn_blocking — and structured observability via
tracing.
Out of scope: raw async/concurrency primitives (channels, locks, task spawning) belong to
rust-async-concurrency; cross-domain crate selection belongs to rust-ecosystem-crates.
Rust Web Backend
Agent Workflow (MANDATORY)
Before building the service, spawn in parallel:
- fuse-ai-pilot:explore-codebase — detect the existing router, state, and DB layer
- fuse-ai-pilot:research-expert — verify current axum/sqlx APIs via Context7/Exa (axum 0.8 changed several APIs)
- mcp__context7__query-docs — pull exact extractor/handler signatures
After building, run fuse-ai-pilot:sniper.
The 2026 standard stack
| Layer |
Crate |
Why |
| Runtime |
tokio |
De-facto async runtime; everything targets it |
| HTTP framework |
axum 0.8.x |
Tower-based, extractor ergonomics, minimal magic |
| Middleware |
tower / tower-http |
Composable layers (trace, cors, timeout, compression) |
| Database |
sqlx 0.9 |
Async, query! compile-time-checked SQL, no DSL; Postgres/MySQL/SQLite |
| Observability |
tracing + tracing-subscriber |
Structured, async-aware spans and logs |
Honest alternatives: sea-orm (ActiveRecord-style ORM, higher-level than sqlx), diesel 2.x (mature, synchronous — needs spawn_blocking or a sync pool in async apps). Prefer sqlx for the standard stack; reach for these only when their model fits.
Critical Rules
- axum 0.8 path syntax is
/{id}, not /:id — /*rest became /{*rest}. The old matchit syntax will not compile.
- Errors implement
IntoResponse — never .unwrap() in a handler. Map domain errors to a status + body via one app error type.
- Share state with
State<T>, wrapped once — put the pool/config in an Arc-friendly struct; extract it with State, do not use globals.
sqlx::query! needs DATABASE_URL at compile time — or a committed .sqlx/ offline cache (cargo sqlx prepare). Plan this before CI.
- No
#[async_trait] on axum extractors — 0.8 uses RPITIT; custom FromRequestParts impls must drop the macro.
Reference Guide
Concepts
| Topic |
Reference |
When to Consult |
| Architecture |
architecture.md |
Router, extractors, State, tower middleware layout |
| Error handling |
error-handling.md |
App error type + IntoResponse |
| Database |
database.md |
sqlx pool, query!, migrations, alternatives |
| Observability |
observability.md |
tracing spans, subscriber, request logging |
Templates
| Template |
When to Use |
| rest-service.md |
Complete minimal REST service (router + state + handlers + errors + tracing) |
Quick Reference
Router with the 0.8 path syntax
let app = Router::new()
.route("/users", get(list).post(create))
.route("/users/{id}", get(show)) // NOT /:id
.with_state(state);
→ See architecture.md
Compile-time-checked query
let user = sqlx::query_as!(User, "SELECT id, name FROM users WHERE id = $1", id)
.fetch_optional(&pool)
.await?;
→ See database.md
Best Practices
DO
- Keep one app error type that implements
IntoResponse.
- Layer cross-cutting concerns (trace, timeout, cors) via
tower-http.
- Verify queries at compile time with
query!/query_as!.
DON'T
- Use
/:id route syntax (0.7 and earlier only).
.unwrap() in handlers — return a typed error.
- Reach for diesel in async code without accounting for its sync nature.
Sources (verified)
- tokio.rs/blog/2025-01-01-announcing-axum-0-8-0 — path syntax, Option extractor,
#[async_trait] removal (fetched 2026-07-05)
- crates.io — axum 0.8.9, sqlx 0.9.0, tokio 1.52.3, tracing 0.1.44 (current at fetch)
1---2name: rust-web-backend3description: Use when building a REST/HTTP backend in Rust — axum routing, extractors, shared state, middleware, error responses, sqlx database access. Not for raw async/concurrency (rust-async-concurrency).4---56<objective>7This skill covers the 2026 standard Rust web-backend stack: axum 0.8.x routing (including8the /{id} path syntax that replaced /:id) and extractors, sharing state via State<T>,9composing tower-http middleware (trace, cors, timeout, compression), mapping domain errors10to IntoResponse instead of unwrapping in handlers, and sqlx 0.9 database access with11compile-time-checked query!/query_as! (including the .sqlx/ offline cache for CI).1213It also names the honest alternatives — sea-orm for an ActiveRecord-style ORM, diesel for a14mature but synchronous alternative needing spawn_blocking — and structured observability via15tracing.1617Out of scope: raw async/concurrency primitives (channels, locks, task spawning) belong to18rust-async-concurrency; cross-domain crate selection belongs to rust-ecosystem-crates.19</objective>2021# Rust Web Backend2223## Agent Workflow (MANDATORY)2425Before building the service, spawn in parallel:26271. **fuse-ai-pilot:explore-codebase** — detect the existing router, state, and DB layer282. **fuse-ai-pilot:research-expert** — verify current axum/sqlx APIs via Context7/Exa (axum 0.8 changed several APIs)293. **mcp__context7__query-docs** — pull exact extractor/handler signatures3031After building, run **fuse-ai-pilot:sniper**.3233---3435## The 2026 standard stack3637| Layer | Crate | Why |38|-------|-------|-----|39| **Runtime** | tokio | De-facto async runtime; everything targets it |40| **HTTP framework** | axum 0.8.x | Tower-based, extractor ergonomics, minimal magic |41| **Middleware** | tower / tower-http | Composable layers (trace, cors, timeout, compression) |42| **Database** | sqlx 0.9 | Async, `query!` compile-time-checked SQL, no DSL; Postgres/MySQL/SQLite |43| **Observability** | tracing + tracing-subscriber | Structured, async-aware spans and logs |4445**Honest alternatives:** `sea-orm` (ActiveRecord-style ORM, higher-level than sqlx), `diesel` 2.x (mature, **synchronous** — needs `spawn_blocking` or a sync pool in async apps). Prefer sqlx for the standard stack; reach for these only when their model fits.4647---4849## Critical Rules50511. **axum 0.8 path syntax is `/{id}`, not `/:id`** — `/*rest` became `/{*rest}`. The old `matchit` syntax will not compile.522. **Errors implement `IntoResponse`** — never `.unwrap()` in a handler. Map domain errors to a status + body via one app error type.533. **Share state with `State<T>`, wrapped once** — put the pool/config in an `Arc`-friendly struct; extract it with `State`, do not use globals.544. **`sqlx::query!` needs `DATABASE_URL` at compile time** — or a committed `.sqlx/` offline cache (`cargo sqlx prepare`). Plan this before CI.555. **No `#[async_trait]` on axum extractors** — 0.8 uses RPITIT; custom `FromRequestParts` impls must drop the macro.5657---5859## Reference Guide6061### Concepts6263| Topic | Reference | When to Consult |64|-------|-----------|-----------------|65| **Architecture** | [architecture.md](references/architecture.md) | Router, extractors, State, tower middleware layout |66| **Error handling** | [error-handling.md](references/error-handling.md) | App error type + `IntoResponse` |67| **Database** | [database.md](references/database.md) | sqlx pool, `query!`, migrations, alternatives |68| **Observability** | [observability.md](references/observability.md) | tracing spans, subscriber, request logging |6970### Templates7172| Template | When to Use |73|----------|-------------|74| [rest-service.md](references/templates/rest-service.md) | Complete minimal REST service (router + state + handlers + errors + tracing) |7576---7778## Quick Reference7980### Router with the 0.8 path syntax8182```rust83let app = Router::new()84 .route("/users", get(list).post(create))85 .route("/users/{id}", get(show)) // NOT /:id86 .with_state(state);87```8889→ See [architecture.md](references/architecture.md)9091### Compile-time-checked query9293```rust94let user = sqlx::query_as!(User, "SELECT id, name FROM users WHERE id = $1", id)95 .fetch_optional(&pool)96 .await?;97```9899→ See [database.md](references/database.md)100101---102103## Best Practices104105### DO106- Keep one app error type that implements `IntoResponse`.107- Layer cross-cutting concerns (trace, timeout, cors) via `tower-http`.108- Verify queries at compile time with `query!`/`query_as!`.109110### DON'T111- Use `/:id` route syntax (0.7 and earlier only).112- `.unwrap()` in handlers — return a typed error.113- Reach for diesel in async code without accounting for its sync nature.114115---116117## Sources (verified)118119- tokio.rs/blog/2025-01-01-announcing-axum-0-8-0 — path syntax, Option extractor, `#[async_trait]` removal (fetched 2026-07-05)120- crates.io — axum 0.8.9, sqlx 0.9.0, tokio 1.52.3, tracing 0.1.44 (current at fetch)