All Rust templates

Rust + Axum Web Server

Axum routers, extractors, middleware, and structured error responses.

DevZone Tools1,690 copiesUpdated Mar 21, 2026Rust
# 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