Rust + Axum Web Server
Axum routers, extractors, middleware, and structured error responses.
# CLAUDE.md — Rust + Axum Web Server
## Stack
- **axum** for routing + handlers, **tokio** for async runtime, **tower** + **tower-http** for middleware.
- **tracing** for logs (with `tracing-subscriber` for setup).
- **sqlx** or **sea-orm** for the database. Pick one.
## Server skeleton
```rust
let state = AppState::new().await?;
let app = Router::new()
.route("/users", get(list_users).post(create_user))
.route("/users/:id", get(get_user))
.layer(TraceLayer::new_for_http())
.layer(CorsLayer::permissive()) // tighten in prod
.with_state(state);
let listener = TcpListener::bind("0.0.0.0:8000").await?;
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await?;
```
- Always use `with_graceful_shutdown`. Listen for `SIGTERM`/`SIGINT` and drain in-flight requests.
## Handlers
- A handler is `async fn` that returns something implementing `IntoResponse`.
- Extractors do the parsing — `Path`, `Query`, `Json`, `State`, custom `FromRequest` impls.
- Return `Result<Json<T>, AppError>`. Map your error to a status via `IntoResponse`.
## State
- Build a single `AppState` struct that holds the DB pool, config, http client, etc.
- Wrap shared mutable state in `Arc<Mutex<...>>` only when you've thought it through. Most of your state is immutable.
- Inject with `State<AppState>`. Don't reach for globals.
## Errors
```rust
#[derive(thiserror::Error, Debug)]
pub enum AppError {
#[error("not found")]
NotFound,
#[error("validation: {0}")]
Validation(String),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, msg) = match self {
AppError::NotFound => (StatusCode::NOT_FOUND, "not found"),
AppError::Validation(_) => (StatusCode::BAD_REQUEST, "bad request"),
AppError::Other(_) => (StatusCode::INTERNAL_SERVER_ERROR, "internal"),
};
(status, msg).into_response()
}
}
```
- Log the underlying error server-side. Never echo internal errors to clients.
## Middleware
- Use `tower-http` for the standard set: tracing, CORS, compression, body limits, request id.
- Order: outer layers run first on request, last on response. The convention is: trace → request id → CORS → body limit → app.
- Custom middleware: implement `tower::Layer` + `tower::Service`, or use `axum::middleware::from_fn` for one-offs.
## Validation
- Use **garde** or **validator** for declarative input validation.
- Validate immediately after extraction. Return 400 with field-level errors.
## Testing
- `axum::Router` is testable directly. Use `tower::ServiceExt::oneshot`:
```rust
let response = app.oneshot(Request::get("/users/123").body(Body::empty())?).await?;
assert_eq!(response.status(), StatusCode::OK);
```
- For integration tests, start a real DB (testcontainers) and a real listener on port 0.
## Don't
- Don't share a single `Arc<Mutex<DbPool>>`. Pools are already concurrent — wrap once and clone the `Arc`.
- Don't block in async handlers. Use `tokio::task::spawn_blocking` for CPU work.
- Don't return `Result<impl IntoResponse, anyhow::Error>` directly to clients — `anyhow` is for app code; use a typed error at the boundary.
- Don't enable `CorsLayer::permissive()` in production.
Other Rust templates
Modern Rust Rules
Idiomatic Rust: ownership, error handling with ?, traits, modules, cargo.
Rust + Tokio Async Patterns
Tokio fundamentals: tasks, select!, channels, cancellation, join!.
Rust CLI Tools (clap)
Build polished CLIs with clap derive, anyhow, and structured logging.
Rust Testing Patterns
Unit tests, integration tests, doctests, proptest, and CI patterns.