Updated Aug 27, 2026

10. Extractors

📋 Jump to Takeaways

🎁 Every parameter in an Axum handler that isn't a plain type is an extractor. Path, Query, Json, State — they all implement the same trait. You can write your own, and that's where the real power is.

How Extractors Work

An extractor implements FromRequestParts (for things that only need headers/path/query) or FromRequest (for things that need the body). Axum calls them in order before your handler runs.

If any extractor fails, the handler never runs — Axum returns the rejection response immediately.

Built-in Extractors Recap

async fn handler(
    State(state): State<AppState>,      // app state
    Path(id): Path<i64>,                // /items/:id
    Query(params): Query<Pagination>,   // ?page=1&limit=10
    Json(body): Json<CreateItem>,       // request body
) -> impl IntoResponse {
    // all four extracted before this runs
}

Order matters for FromRequest extractors (like Json) — they consume the body, so they must come last.

Custom Extractor: Validated Input

The Json extractor deserializes but doesn't validate. Wrap it to add validation:

use axum::{extract::{FromRequest, Request}, response::{IntoResponse, Response}};
use serde::de::DeserializeOwned;
use validator::Validate;

pub struct ValidatedJson<T>(pub T);

impl<T, S> FromRequest<S> for ValidatedJson<T>
where
    T: DeserializeOwned + Validate,
    S: Send + Sync,
{
    type Rejection = Response;

    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
        let Json(value) = Json::<T>::from_request(req, state)
            .await
            .map_err(|e| e.into_response())?;

        value.validate()
            .map_err(|e| AppError::InvalidInput(e.to_string()).into_response())?;

        Ok(ValidatedJson(value))
    }
}

Add validator = "0.18" to Cargo.toml, then use it on your request structs:

use validator::Validate;

#[derive(Deserialize, Validate)]
pub struct CreateLink {
    #[validate(url)]
    pub url: String,

    #[validate(length(max = 50))]
    pub label: Option<String>,
}

async fn create_link(
    State(state): State<AppState>,
    ValidatedJson(body): ValidatedJson<CreateLink>,
) -> Result<(StatusCode, Json<Link>), AppError> {
    // body.url is guaranteed valid here
    let link = create(&state.pool, &body.url).await?;
    Ok((StatusCode::CREATED, Json(link)))
}

Custom Extractor: Auth Token

Extract and verify a bearer token without repeating the logic in every handler:

use axum::{extract::FromRequestParts, http::{request::Parts, StatusCode}};

pub struct AuthUser {
    pub user_id: String,
}

impl<S> FromRequestParts<S> for AuthUser
where
    S: Send + Sync,
{
    type Rejection = StatusCode;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        let token = parts
            .headers
            .get("authorization")
            .and_then(|v| v.to_str().ok())
            .and_then(|v| v.strip_prefix("Bearer "))
            .ok_or(StatusCode::UNAUTHORIZED)?;

        // verify_token(token, secret) — full implementation with state in lesson 12
        let user_id = verify_token(token, "secret")
            .map(|claims| claims.sub)
            .ok_or(StatusCode::UNAUTHORIZED)?;

        Ok(AuthUser { user_id })
    }
}

Now any handler that declares AuthUser as a parameter is automatically protected:

async fn delete_link(
    State(state): State<AppState>,
    AuthUser { user_id }: AuthUser,  // 401 if no valid token
    Path(code): Path<String>,
) -> Result<StatusCode, AppError> {
    // only reaches here with a valid token
    delete_by_code(&state.pool, &code, user_id).await?;
    Ok(StatusCode::NO_CONTENT)
}

Key Takeaways

  • All handler parameters except plain types are extractors implementing FromRequest or FromRequestParts
  • FromRequestParts reads headers/path/query without consuming the body; FromRequest can read the body
  • Json extractors must come last — they consume the body
  • Custom extractors centralize logic like validation and authentication so handlers stay clean
  • If any extractor's from_request returns Err, the handler never runs

🎁 Your service works. Now: how do you know it keeps working after you change something? Next up: testing Axum handlers directly, without spinning up a server.

📝 Ready to test your knowledge?

Answer the quiz below to mark this lesson complete.

Spot something off? Report an issue
© 2026 ByteLearn.dev. Free courses for developers. · Privacy