← Week 3: Axum Web Framework

Day 16: Axum Extractors

Phase 2 · Jun 25, 2026

← Week 3: Axum Web Framework

Agenda (2–3 hours)

  • Read (45 min): axum::extract module docs; FromRequest and FromRequestParts traits
  • Study (45 min): Why does Json<T> implement FromRequest but Path<T> implements FromRequestParts? What's the difference?
  • Practice (45 min): Build a handler that uses State, Path, Query, Headers, and Json in one function; test it with axum_test or reqwest
  • Challenge (30 min): Implement a custom extractor that reads a X-Request-Id header and generates one if absent
← Week 3: Axum Web Framework

Built-in Extractors

// Path parameters (/users/:id)
Path(id): Path<u64>

// Query string (/search?q=hello&page=2)
Query(params): Query<SearchParams>

// Request body as JSON
Json(body): Json<CreateRequest>

// Shared application state
State(state): State<Arc<AppState>>

// Raw headers
headers: HeaderMap

// Client IP
ConnectInfo(addr): ConnectInfo<SocketAddr>

// Authentication header (via axum-extra)
TypedHeader(auth): TypedHeader<Authorization<Bearer>>
← Week 3: Axum Web Framework

State Extractor

#[derive(Clone)]
struct AppState {
    db: Arc<DatabasePool>,
    config: Arc<Config>,
}

let state = AppState { db: db_pool, config: Arc::new(config) };

let app = Router::new()
    .route("/users", get(list_users))
    .with_state(state);

async fn list_users(State(state): State<AppState>) -> Json<Vec<User>> {
    let users = state.db.query_all().await;
    Json(users)
}

State is cloned for each request — keep it cheap to clone (wrap heavy resources in Arc).

← Week 3: Axum Web Framework

FromRequestParts vs FromRequest

// FromRequestParts: can extract without consuming the body
// Used for: Path, Query, Headers, State, ConnectInfo
pub trait FromRequestParts<S>: Sized {
    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection>;
}

// FromRequest: may consume the body
// Used for: Json, Bytes, String, Form
pub trait FromRequest<S, M = ViaRequest>: Sized {
    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection>;
}

Because only one extractor can consume the body, Json (or Bytes) must be last in the handler parameter list.

← Week 3: Axum Web Framework

Custom Extractor

struct RequestId(String);

#[async_trait]
impl<S: Send + Sync> FromRequestParts<S> for RequestId {
    type Rejection = Infallible;

    async fn from_request_parts(parts: &mut Parts, _state: &S)
        -> Result<Self, Infallible>
    {
        let id = parts
            .headers
            .get("x-request-id")
            .and_then(|v| v.to_str().ok())
            .unwrap_or_default()
            .to_string()
            .or_else(|| Uuid::new_v4().to_string());
        Ok(RequestId(id))
    }
}
← Week 3: Axum Web Framework

Key Takeaways

  • Extractors are compile-time type-safe: wrong body type → rejection response automatically
  • State<T> is the idiomatic way to share resources (DB pool, config) across handlers
  • FromRequestParts vs FromRequest distinction determines whether the body is consumed
  • Custom extractors are straightforward to implement and compose with built-ins

Tomorrow: middleware in Axum — adding cross-cutting concerns.