Routing & Handler Signatures

1. Welcome and Icebreaker:

What’s your favorite HTTP method (GET, POST, PUT, DELETE, etc.) and why?

2. Outline the Session:

Agenda:

  1. Concept Explanation: Routing in Axum and handler function signatures.
  2. Live Coding: Add multiple routes and experiment with handler return types.
  3. Tasks: Practice by creating your own routes and handlers.

Learning Outcomes:

  • Understand how to define routes for different HTTP methods.
  • Learn about handler function signatures and return types in Axum.

Expectations: Feel free to experiment and ask questions!

3. Concept Explanation:

Routing is how Axum directs incoming HTTP requests to the correct handler function. Each route is associated with an HTTP method (GET, POST, etc.) and a path. Handlers are async functions that process requests and return responses. Axum supports various return types, such as String, &'static str, or any type that implements IntoResponse.

You can also nest routers for better organization, and use async functions for all handlers.

4. Live Coding Demonstration:

Let’s add more routes, see different handler signatures, and demonstrate nesting routers.

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

async fn hello() -> &'static str {
    "Hello, Axum!"
}

async fn create() -> String {
    "Created!".to_string()
}

async fn update() -> &'static str {
    "Updated!"
}

async fn ping() -> &'static str {
    "pong"
}

#[tokio::main]
async fn main() {
    // Create a nested API router
    let api_router = Router::new()
        .route("/ping", get(ping));

    let app = Router::new()
        .route("/", get(hello))
        .route("/create", post(create))
        .route("/update", put(update))
        .nest("/api", api_router);
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Step-by-step explanation:

  • We define handlers for different routes and HTTP methods: hello (GET), create (POST), update (PUT), and ping (GET under /api).
  • The update handler demonstrates a PUT route.
  • We create a nested router for /api and add a /api/ping route.
  • Handlers return different types (String, &'static str), showing Axum’s flexibility with any type that implements IntoResponse.
  • All handler functions are async.

Best Practices:

  • Prefer resource-oriented route names for real APIs (e.g., POST /items, PUT /items/{id}); /create and /update here only illustrate method routing.
  • Return types should implement IntoResponse for flexibility (e.g., String, &'static str, or custom types).
  • Use router nesting to organize related endpoints (e.g., group all API routes under /api).
  • Keep handler functions small and focused.

Common Pitfalls:

  • Mismatching HTTP method and handler (e.g., using GET for a create operation).
  • Forgetting to make handler functions async.
  • Not using router nesting for related endpoints, leading to a flat and hard-to-maintain route structure.
  • Not handling different response types properly (e.g., always returning &'static str when you may need to return a Result).

5. Tasks

  • Add a new PUT route /update that returns a message like “Updated!”.
  • Try nesting a router under /api and add a route /api/ping that returns “pong”.
  • Experiment with returning different types from handlers (e.g., String, &'static str).
  • Bonus: Try returning a custom struct as JSON from a handler using Json from axum::Json.
  • Bonus: Add error handling to a handler and return a custom error response.