Extractors & State
Axum handlers receive request data through extractors - State, Json, Path, Query, headers, and more.
Busca en todas las páginas de la documentación
Axum handlers receive request data through extractors - State, Json, Path, Query, headers, and more.
Quick-reference recipe card - copy-paste ready.
use axum::extract::{Path, Query, State};
use axum::Json;
use serde::Deserialize;
#[derive(Clone)]
struct AppState { pool: sqlx::PgPool }
#[derive(Deserialize)]
struct Search { q: Option<String> }
async fn handler(
State(state): State<AppState>,
Path(id): Path<u64>,
Query(search): Query<Search>,
Json(body): Json<CreateDto>,
) -> Result<Json<Item>, AppError> {
// use state.pool, id, search.q, body
}When to reach for this: Every Axum handler that reads path, query, body, or shared application data.
use axum::{
extract::{Path, Query, State},
routing::get,
Json, Router,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
items: Arc<tokio::sync::RwLock<Vec<String>>>,
}
#[derive(Deserialize)]
struct Filter { prefix: Option<String> }
#[derive(Serialize)]
struct ItemView { index: usize, name: String }
async fn list(
State(state): State<AppState>,
Query(filter): Query<Filter>,
) -> Json<Vec<ItemView>> {
let items = state.items.read().await;
let views: Vec<_> = items
.iter()
.enumerate()
.filter(|(_, name)| {
filter.prefix.as_ref().map(|p| name.starts_with(p)).unwrap_or(true)
})
.map(|(i, name)| ItemView { index: i, name: name.clone() })
.collect();
Json(views)
}
async fn get_one(Path(index): Path<usize>, State(state): State<AppState>) -> Json<ItemView> {
let items = state.items.read().await;
let name = items.get(index).cloned().unwrap_or_default();
Json(ItemView { index, name })
}
#[tokio::main]
async fn main() {
let state = AppState {
items: Arc::new(tokio::sync::RwLock::new(vec!["alpha".into(), "beta".into()])),
};
let app = Router::new()
.route("/items", get(list))
.route("/items/:index", get(get_one))
.with_state(state);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}What this demonstrates:
State shares Arc-wrapped data across handlers.Query drives optional filtering without new routes.Path captures resource identifiers from the URL.FromRequestParts or FromRequest and run before the handler body.State<T> clones T per request - use Arc for pools, clients, and config.| Extractor | Source | Typical use |
|---|---|---|
Path<T> | URL segments | Resource IDs |
Query<T> | Query string | Filters, pagination |
Json<T> | Body | POST/PATCH payloads |
State<T> | Router state | DB pool, config |
HeaderMap | Headers | Auth tokens, tracing |
// Optional extractor - entire param can be absent
use axum::extract::OptionalQuery;
async fn maybe(Query(q): OptionalQuery<Filter>) -> String { /* ... */ }AppState with non-Clone fields fails to compile. Fix: Wrap in Arc or Arc<Mutex<_>>.Body extractors consume the body; only one body extractor per handler. Fix: Use a single Json or custom FromRequest..with_state() on the parent. Fix: Call .with_state() once on the top-level router.#[serde(deny_unknown_fields)] intentionally or allow extras./users/:id with Path<String> when clients send numbers. Fix: Pick the correct type or validate manually.| Alternative | Use When | Don't Use When |
|---|---|---|
| Request extensions | Per-request metadata from middleware | Shared app-wide config |
Extension<T> (deprecated pattern) | Legacy Tower apps | New Axum 0.8 projects - prefer State |
Manual Request parsing | Full control over body streaming | Standard JSON APIs |
Axum 0.8 uses State for application data; avoid deprecated Extension for new code.
Combine into one AppState struct - Axum supports one state type per router.
Open in handler from pool in State, or use middleware that inserts a transaction extension.
Implement FromRequestParts for AuthUser reading the Authorization header.
Use axum::extract::Multipart for file fields.
Use Bytes extractor from axum::body.
Use validator crate on the Query struct or manual checks in the handler.
Build router with .with_state(test_state) in each test module.
Use axum_extra::extract::CookieJar from the axum-extra crate.
Implement IntoResponse on custom rejection types for extractors.
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Revisado por Chris St. John·Última actualización: 19 jul 2026