Axum: Shared State and Middleware
Use case
You need shared state (e.g. a database pool or config) and cross-cutting behavior (logging, auth) in your API. Axum uses the State extractor for shared state and Tower layers for middleware.
In C#: Like dependency injection (services) and middleware pipeline in ASP.NET Core. In Rust, state is passed explicitly; middleware is Tower layers.
Shared state with State
Clone your state (or wrap in Arc) and pass it to the router with .with_state(state). Handlers take State<YourState> to access it.
use axum::{Router, extract::State};
use std::sync::Arc;
struct AppState {
counter: std::sync::atomic::AtomicU32,
}
async fn handler(State(state): State<Arc<AppState>>) -> String {
let n = state.counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
format!("count: {}", n)
}
let state = Arc::new(AppState { counter: Default::default() });
let app = Router::new().route("/", axum::routing::get(handler)).with_state(state);In C#: Like injecting a service via constructor; in Rust the state is cloned into the router and each request gets a reference (e.g. via Arc).
Middleware (Tower layers)
Tower provides a layered service model. Use tower_http::trace::TraceLayer for request logging, or custom layers that wrap the request/response. Add a layer to the router with .layer(...).
use tower_http::trace::TraceLayer;
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http());In C#: Like app.UseMiddleware<LoggingMiddleware>(). Layers run in order; the last added runs first (outermost).
Key takeaway
Use .with_state(arc_state) to share state; use State<T> in handlers. Use .layer(layer) to add middleware (e.g. TraceLayer). State must be Clone (or wrap in Arc); layers implement Tower's Service trait.