Skip to main content
Routing is the process of matching incoming HTTP requests to handler functions based on the request’s path and method. Axum provides a powerful and flexible routing system through the Router type.

Creating a router

Create a new router using Router::new():
use axum::Router;

let app = Router::new();
A newly created router will respond with 404 Not Found to all requests until you add routes.

Adding routes

Use the route method to add routes to your router. Each route consists of a path pattern and a MethodRouter that defines which HTTP methods are handled:
use axum::{Router, routing::get};

let app = Router::new()
    .route("/", get(|| async { "Hello, World!" }));

HTTP method routing

Axum provides convenience functions for each HTTP method:
use axum::{Router, routing::get};

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

async fn list_users() -> &'static str {
    "User list"
}
GET routes also handle HEAD requests automatically, but with the response body removed.

Path patterns

Axum supports dynamic path segments using named parameters:
use axum::{Router, routing::get, extract::Path};

// Single parameter
let app = Router::new()
    .route("/users/:id", get(get_user));

async fn get_user(Path(id): Path<String>) -> String {
    format!("User ID: {}", id)
}

// Multiple parameters
let app = Router::new()
    .route("/users/:user_id/team/:team_id", get(get_team));

async fn get_team(Path((user_id, team_id)): Path<(String, String)>) -> String {
    format!("User {} in team {}", user_id, team_id)
}

Wildcard captures

Capture the rest of the path with wildcard parameters:
use axum::{Router, routing::get, extract::Path};

let app = Router::new()
    .route("/files/*path", get(serve_file));

async fn serve_file(Path(path): Path<String>) -> String {
    format!("Serving file: {}", path)
}

Combining routes

Chaining routes

Chain multiple route calls to add multiple routes:
use axum::{Router, routing::{get, post, delete}};

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

Merging routers

Combine multiple routers with merge:
use axum::{Router, routing::get};

let user_routes = Router::new()
    .route("/users", get(list_users))
    .route("/users/:id", get(get_user));

let post_routes = Router::new()
    .route("/posts", get(list_posts))
    .route("/posts/:id", get(get_post));

let app = Router::new()
    .merge(user_routes)
    .merge(post_routes);

Nesting routers

Nest a router under a path prefix:
use axum::{Router, routing::get};

let api_routes = Router::new()
    .route("/users", get(list_users))
    .route("/posts", get(list_posts));

let app = Router::new()
    .nest("/api", api_routes);

// Routes are now available at:
// - /api/users
// - /api/posts
Nesting at the root ("/") is not supported. Use merge instead.

Fallback handlers

Handle requests that don’t match any route:
use axum::{Router, routing::get, http::StatusCode};

let app = Router::new()
    .route("/", get(|| async { "Home" }))
    .fallback(|| async { (StatusCode::NOT_FOUND, "Not found") });

Route services

For more control, you can route to any Tower Service:
use axum::{Router, routing::get_service};
use tower::service_fn;
use http::Response;
use std::convert::Infallible;

let service = tower::service_fn(|_req| async {
    Ok::<_, Infallible>(Response::new(axum::body::Body::empty()))
});

let app = Router::new()
    .route("/", get_service(service));

Placeholder handlers

You can pass types that implement IntoResponse directly as handlers:
use axum::{Router, routing::get, http::StatusCode, Json};
use serde_json::json;

let app = Router::new()
    // Return a fixed string
    .route("/", get("Hello, World!"))
    // Return mock data
    .route("/users", get((
        StatusCode::OK,
        Json(json!({ "id": 1, "name": "alice" })),
    )));

Next steps

Handlers

Learn about handler functions and their signatures

State

Share state across handlers using with_state