← Week 3: Axum Web Framework

Day 15: Axum Fundamentals

Phase 2 · Jun 24, 2026

← Week 3: Axum Web Framework

Agenda (2–3 hours)

  • Read (45 min): Axum README and examples; axum::routing module docs
  • Study (45 min): How does Axum convert a handler function into a Service? Trace the type chain from Router to hyper::Server
  • Practice (45 min): Build an Axum server with five routes: GET/POST/PUT/DELETE for a resource, plus a 404 fallback
  • Challenge (30 min): What is the MethodRouter and how does it differ from Router? When would you use .merge() vs .nest()?
← Week 3: Axum Web Framework

Axum at a Glance

Axum is an HTTP framework built on Tower + Hyper. Key properties:

  • No macros: routing is code, not annotations
  • Type-safe extractors: request parts are extracted via trait, not reflection
  • Tower native: middleware is standard Tower layers; works with any Tower ecosystem tool
  • Fast: Hyper underneath; async from top to bottom
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
tower = "0.4"
← Week 3: Axum Web Framework

Router

use axum::{Router, routing::{get, post, delete}};

let app = Router::new()
    .route("/users", get(list_users).post(create_user))
    .route("/users/:id", get(get_user).put(update_user).delete(delete_user))
    .fallback(handler_404);

axum::serve(TcpListener::bind("0.0.0.0:3000").await?, app).await?;

Routes are matched in definition order. :id is a path parameter.

← Week 3: Axum Web Framework

Handler Functions

Any async function that takes extractors and returns IntoResponse is a handler:

async fn list_users() -> Json<Vec<User>> {
    Json(vec![])
}

async fn get_user(Path(id): Path<u64>) -> Result<Json<User>, StatusCode> {
    let user = db::find(id).await.ok_or(StatusCode::NOT_FOUND)?;
    Ok(Json(user))
}

async fn create_user(Json(body): Json<CreateUserRequest>) -> (StatusCode, Json<User>) {
    let user = db::create(body).await;
    (StatusCode::CREATED, Json(user))
}
← Week 3: Axum Web Framework

IntoResponse

Any type implementing IntoResponse can be returned from a handler:

// Built-in IntoResponse impls:
// StatusCode, (StatusCode, Body), Json<T>, Html<T>, String, &str, Vec<u8>

// Custom:
impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response()
    }
}
← Week 3: Axum Web Framework

Key Takeaways

  • Axum's Router is a Tree router: O(log n) dispatch, no regex overhead
  • Handlers are type-checked at compile time via the Handler trait — wrong signature = compile error
  • IntoResponse is the universal response abstraction — implement it for your error type
  • Axum is Tower-native: Router implements Service, composable with any Tower middleware

Tomorrow: extractors — pulling data out of requests in a type-safe way.