Loading page…
Rust walkthroughs
Loading page…
axum::Router::nest differ from axum::Router::merge for composing routes?axum::Router::nest mounts a router at a path prefix, where the nested router's routes are prefixed but the prefix is stripped before matching within the nested router. axum::Router::merge combines two routers at the same path level, where all routes from the merged router are added directly without path modification. The key difference is that nest creates a hierarchical path structure (/api/users from nest at /api with route /users), while merge flattens routes into the existing router at their original paths. Use nest when you want to organize routers under a common prefix with shared middleware or state; use merge when combining independent routers that should coexist at the same routing level.
use axum::{Router, routing::get, Json};
async fn handler() -> Json<&'static str> {
Json("Hello")
}
fn main() {
let app = Router::new()
.route("/hello", get(handler));
}Both nest and merge are for composing multiple routers together.
use axum::{Router, routing::get, Json};
async fn users() -> Json<&'static str> {
Json("Users")
}
async fn posts() -> Json<&'static str> {
Json("Posts")
}
async fn comments() -> Json<&'static str> {
Json("Comments")
}
fn main() {
let users_router = Router::new()
.route("/users", get(users));
let posts_router = Router::new()
.route("/posts", get(posts))
.route("/comments", get(comments));
// merge combines routes at the same level
let app = Router::new()
.merge(users_router)
.merge(posts_router);
// Routes available:
// GET /users
// GET /posts
// GET /comments
}merge adds all routes from one router to another at their original paths.
use axum::{Router, routing::get, Json};
async fn users() -> Json<&'static str> {
Json("Users")
}
async fn user_detail() -> Json<&'static str> {
Json("User Detail")
}
fn main() {
let users_router = Router::new()
.route("/", get(users))
.route("/:id", get(user_detail));
let app = Router::new()
.nest("/api/users", users_router);
// Routes available:
// GET /api/users/ -> users_router route "/"
// GET /api/users/:id -> users_router route "/:id"
}nest prefixes all routes in the nested router with the given path.
use axum::{Router, routing::get, Json};
use axum::extract::Path;
async fn user_by_id(Path(id): Path<String>) -> Json<String> {
Json(format!("User {}", id))
}
fn main() {
let users_router = Router::new()
.route("/:id", get(user_by_id));
let app = Router::new()
.nest("/users", users_router);
// Request: GET /users/123
// Nest strips "/users", then routes to "/:id"
// The handler receives "123" as the path parameter
}The nested router sees paths without the nesting prefix.
use axum::{Router, routing::get, Json};
use axum::extract::Path;
async fn user_by_id(Path(id): Path<String>) -> Json<String> {
Json(format!("User {}", id))
}
fn main() {
let users_router = Router::new()
.route("/users/:id", get(user_by_id));
let app = Router::new()
.merge(users_router);
// Request: GET /users/123
// merge keeps original path, routes to "/users/:id"
// The handler receives "123" as the path parameter
}Merged routes keep their exact original paths.
use axum::{Router, routing::get, Json};
async fn list_users() -> Json<&'static str> {
Json("Users v1")
}
async fn get_user() -> Json<&'static str> {
Json("User v1")
}
async fn list_users_v2() -> Json<&'static str> {
Json("Users v2")
}
fn main() {
let v1_router = Router::new()
.route("/users", get(list_users))
.route("/users/:id", get(get_user));
let v2_router = Router::new()
.route("/users", get(list_users_v2));
let app = Router::new()
.nest("/v1", v1_router)
.nest("/v2", v2_router);
// Routes:
// GET /v1/users
// GET /v1/users/:id
// GET /v2/users
}nest is ideal for organizing routers under prefixes like API versions.
use axum::{Router, routing::get, Json};
async fn health() -> Json<&'static str> {
Json("OK")
}
fn users_routes() -> Router {
Router::new()
.route("/users", get(|| async { "List users" }))
.route("/users/:id", get(|| async { "Get user" }))
}
fn posts_routes() -> Router {
Router::new()
.route("/posts", get(|| async { "List posts" }))
.route("/posts/:id", get(|| async { "Get post" }))
}
fn main() {
let app = Router::new()
.route("/health", get(health))
.merge(users_routes())
.merge(posts_routes());
// Routes:
// GET /health
// GET /users
// GET /users/:id
// GET /posts
// GET /posts/:id
}merge combines routers from different modules at the same level.
use axum::{Router, routing::get, Json};
use tower_http::trace::TraceLayer;
async fn handler() -> Json<&'static str> {
Json("Protected")
}
fn main() {
let api_router = Router::new()
.route("/data", get(handler));
let app = Router::new()
.layer(TraceLayer::new_for_http())
.nest("/api", api_router);
// The TraceLayer applies to ALL routes including nested ones
// GET /api/data has the TraceLayer applied
}Middleware on the parent router applies to nested routers.
use axum::{Router, routing::get, Json};
use tower_http::trace::TraceLayer;
async fn handler() -> Json<&'static str> {
Json("Protected")
}
fn main() {
let api_router = Router::new()
.route("/api/data", get(handler));
let app = Router::new()
.layer(TraceLayer::new_for_http())
.merge(api_router);
// Merged routes inherit middleware from the parent
// GET /api/data has the TraceLayer applied
}Both nest and merge inherit middleware from the parent router.
use axum::{Router, routing::get, Json, Extension};
use std::sync::Arc;
struct AppState {
db: String,
}
async fn handler(Extension(state): Extension<Arc<AppState>>) -> Json<String> {
Json(state.db.clone())
}
fn main() {
let state = Arc::new(AppState { db: "connected".into() });
let nested_router = Router::new()
.route("/info", get(handler));
let app = Router::new()
.nest("/api", nested_router)
.layer(Extension(state));
// State is available in nested router handlers
}State is shared with both nested and merged routers.
use axum::{Router, routing::get};
fn main() {
let router1 = Router::new()
.route("/users", get(|| async { "Router 1" }));
let router2 = Router::new()
.route("/users", get(|| async { "Router 2" }));
// merge will panic at runtime on route conflict
let app = Router::new()
.merge(router1)
.merge(router2);
// Panics: "route collision: `/users` already exists"
}merge panics on route conflicts.
use axum::{Router, routing::get};
fn main() {
let nested1 = Router::new()
.route("/data", get(|| async { "Nested 1" }));
let nested2 = Router::new()
.route("/data", get(|| async { "Nested 2" }));
// Different nest paths avoid conflict
let app = Router::new()
.nest("/api/v1", nested1)
.nest("/api/v2", nested2);
// Routes: /api/v1/data and /api/v2/data - no conflict
}Different nest prefixes prevent conflicts.
use axum::{Router, routing::get, Json};
async fn fallback() -> Json<&'static str> {
Json("Not found")
}
fn main() {
let nested_router = Router::new()
.route("/specific", get(|| async { "Found" }));
let app = Router::new()
.nest("/api", nested_router)
.fallback(fallback);
// Request: GET /api/specific -> "Found"
// Request: GET /api/unknown -> "Not found" (from fallback)
// Request: GET /other -> "Not found" (from fallback)
}Fallback handlers apply after nested routing fails.
use axum::{Router, routing::get};
fn main() {
let inner_router = Router::new()
.route("/item", get(|| async { "Item" }));
let middle_router = Router::new()
.nest("/category", inner_router);
let app = Router::new()
.nest("/api/v1", middle_router);
// Routes:
// GET /api/v1/category/item
// Each nest adds its prefix
}Nesting can be chained for deep hierarchies.
use axum::{Router, routing::get};
fn main() {
let api_routes = Router::new()
.route("/users", get(|| async { "Users" }));
let web_routes = Router::new()
.route("/", get(|| async { "Home" }));
let health_route = Router::new()
.route("/health", get(|| async { "OK" }));
let app = Router::new()
.nest("/api", api_routes) // /api/users
.merge(web_routes) // /
.merge(health_route); // /health
}Combine nest and merge based on organizational needs.
use axum::{Router, routing::get, extract::Path};
async fn get_post(
Path((user_id, post_id)): Path<(String, String)>,
) -> String {
format!("User {} Post {}", user_id, post_id)
}
fn main() {
let posts_router = Router::new()
.route("/:post_id", get(get_post));
let users_router = Router::new()
.nest("/:user_id/posts", posts_router);
let app = Router::new()
.nest("/users", users_router);
// GET /users/123/posts/456
// Parameters: user_id=123, post_id=456
// The /:user_id is in users_router (before the nest of posts_router)
// The /:post_id is in posts_router
}Path parameters accumulate across nesting levels.
use axum::{Router, routing::get};
fn main() {
let nested = Router::new()
.route("/specific", get(|| async { "Nested specific" }));
let app = Router::new()
.route("/api/specific", get(|| async { "Root specific" }))
.nest("/api", nested);
// Request: GET /api/specific
// Matches: "Root specific" (defined before nest)
// The root route takes precedence because it's added first
}Route ordering matters when paths overlap.
use axum::{Router, routing::get};
fn main() {
let router1 = Router::new()
.route("/data", get(|| async { "Router 1" }));
let router2 = Router::new()
.route("/data", get(|| async { "Router 2" }));
// This would panic due to route conflict
// let app = Router::new()
// .merge(router1)
// .merge(router2);
}merge panics on exact route conflicts.
use axum::{Router, routing::get};
fn main() {
// With nest: nested router defines routes without prefix
let api = Router::new()
.route("/users", get(|| async { "Users" }));
// When mounted at "/api", the route becomes "/api/users"
// With merge: routes are defined with full paths
let routes = Router::new()
.route("/api/users", get(|| async { "Users" }));
// Route stays "/api/users"
// For URL generation:
// - nest: prepend the nesting prefix
// - merge: use the route as-is
}Consider URL generation when choosing between nest and merge.
use axum::{Router, routing::get};
fn main() {
// Use nest when:
// 1. Organizing routes under a common prefix
// 2. API versioning
// 3. Grouping routes with shared middleware/state
// 4. Modular code organization where each module defines relative paths
let v1 = Router::new()
.route("/users", get(|| async { "v1 users" }))
.route("/posts", get(|| async { "v1 posts" }));
let v2 = Router::new()
.route("/users", get(|| async { "v2 users" }))
.route("/posts", get(|| async { "v2 posts" }));
let app = Router::new()
.nest("/v1", v1)
.nest("/v2", v2);
// Each version router defines clean, relative paths
// Easy to change version prefix in one place
}Use nest for hierarchical organization.
use axum::{Router, routing::get};
fn main() {
// Use merge when:
// 1. Combining independent routers with different paths
// 2. Adding health/metrics routes alongside API routes
// 3. Modular organization where modules define absolute paths
// 4. Avoiding redundant prefixes in route definitions
let users_api = Router::new()
.route("/users", get(|| async { "users" }));
let posts_api = Router::new()
.route("/posts", get(|| async { "posts" }));
let health = Router::new()
.route("/health", get(|| async { "OK" }));
let app = Router::new()
.merge(users_api)
.merge(posts_api)
.merge(health);
// All routes at same level, no prefix manipulation
}Use merge for flat composition of independent routers.
| Aspect | nest | merge |
|--------|--------|---------|
| Path prefix | Added to all routes | Not modified |
| Route visibility | Nested router sees paths without prefix | Routes keep original paths |
| Primary use case | Hierarchical organization | Flat composition |
| Path parameters | Prefix can contain parameters | No prefix modification |
| Route conflicts | Different prefixes avoid conflicts | Panics on conflicts |
| Middleware | Inherited from parent | Inherited from parent |
| State | Shared from parent | Shared from parent |
nest and merge solve different composition problems:
nest("/prefix", router) creates a hierarchical structure. The nested router defines routes relative to its own context (like /users instead of /api/users), and the parent adds the prefix. This is powerful for:
/v1/usersThe nested router doesn't know about its prefix—it just sees /users/:id, making it reusable at different mount points.
merge(router) flattens routes into the existing router. Each route keeps its original path. This is useful for:
Key insight: nest creates parent-child relationships with prefix semantics; merge creates sibling relationships. When you want to change where a group of routes is mounted, nest lets you change one prefix. When you want to combine routers that should coexist at the same level, merge adds them without modification.
Both inherit middleware and state from the parent, so the difference is purely about path handling and organizational structure.