Extractor for accessing shared application state.
State is global to a router and shared across all requests. It’s the recommended way to share things like database connection pools, API clients, and configuration.
Type signature
pub struct State<S>(pub S);
Basic usage
With Router
use axum::{
Router,
routing::get,
extract::State,
};
// The application state
#[derive(Clone)]
struct AppState {
db: DatabasePool,
api_key: String,
}
let state = AppState {
db: create_pool().await,
api_key: "secret".to_owned(),
};
// Create a router with state
let app = Router::new()
.route("/", get(handler))
.with_state(state);
async fn handler(State(state): State<AppState>) {
// Access the state
let db = &state.db;
let api_key = &state.api_key;
// ...
}
State must implement Clone since it’s cloned for each request. Use Arc for expensive-to-clone types.
With MethodRouter
use axum::{
routing::get,
extract::State,
};
#[derive(Clone)]
struct AppState {}
let state = AppState {};
let method_router_with_state = get(handler)
.with_state(state);
async fn handler(State(state): State<AppState>) {
// ...
}
With Handler
use axum::{
handler::Handler,
extract::State,
};
#[derive(Clone)]
struct AppState {}
async fn handler(State(state): State<AppState>) {
// ...
}
let state = AppState {};
let handler_with_state = handler.with_state(state);
# async {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, handler_with_state.into_make_service()).await;
# };
State is an extractor, so it must appear before any body extractors (like Json, Form, String, etc.) in your handler signature.
// ✅ Correct: State before Json
async fn correct(State(state): State<AppState>, Json(data): Json<Data>) {}
// ❌ Wrong: Json before State
async fn wrong(Json(data): Json<Data>, State(state): State<AppState>) {}
Combining routers
When combining routers with Router::nest or Router::merge, they must have the same state type:
use axum::{Router, routing::get, extract::State};
#[derive(Clone)]
struct AppState {}
let state = AppState {};
// Create a router that will be nested
let api = Router::new()
.route("/posts", get(posts_handler));
let app = Router::new()
.nest("/api", api)
.with_state(state);
async fn posts_handler(State(state): State<AppState>) {
// ...
}
Explicit state types
When composing routers in separate functions, you may need to annotate the state type:
use axum::{Router, routing::get, extract::State};
#[derive(Clone)]
struct AppState {}
fn make_app() -> Router {
let state = AppState {};
Router::new()
.nest("/api", make_api())
.with_state(state)
}
// Explicitly specify Router<AppState> to compose with outer router
fn make_api() -> Router<AppState> {
Router::new()
.route("/posts", get(posts_handler))
}
async fn posts_handler(State(state): State<AppState>) {
// ...
}
Substates
You can extract “substates” using FromRef to provide different state to different parts of your app:
use axum::{
Router,
routing::get,
extract::{State, FromRef},
};
// Main application state
#[derive(Clone)]
struct AppState {
api_state: ApiState,
}
// API-specific state
#[derive(Clone)]
struct ApiState {
quota: u32,
}
// Enable extracting ApiState from AppState
impl FromRef<AppState> for ApiState {
fn from_ref(app_state: &AppState) -> ApiState {
app_state.api_state.clone()
}
}
let state = AppState {
api_state: ApiState { quota: 100 },
};
let app = Router::new()
.route("/", get(handler))
.route("/api/users", get(api_users))
.with_state(state);
async fn handler(State(state): State<AppState>) {
// Access full state
}
async fn api_users(State(api_state): State<ApiState>) {
// Access only API state
}
You can also derive FromRef:
use axum::extract::FromRef;
#[derive(Clone, FromRef)]
struct AppState {
api_state: ApiState,
// Other fields...
}
Shared mutable state
Since state is cloned for each request, you can’t directly get a mutable reference. Use Arc<Mutex<_>> or similar:
use axum::{
Router,
routing::get,
extract::State,
};
use std::sync::{Arc, Mutex};
#[derive(Clone)]
struct AppState {
data: Arc<Mutex<String>>,
}
async fn handler(State(state): State<AppState>) {
let mut data = state.data.lock().unwrap();
*data = "updated value".to_owned();
}
let state = AppState {
data: Arc::new(Mutex::new("initial".to_owned())),
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);
Holding a locked std::sync::Mutex across .await points will result in !Send futures which are incompatible with axum. Use tokio::sync::Mutex if you need to hold a mutex across .await points.
State vs Extension
For global application state shared across all requests, prefer State because:
- Type-safe: The state type is part of the router’s type
- Compile-time checked: Wrong state types are caught at compile time
- Better error messages
Use Extension for request-derived data like authorization info or per-request context.