Routing
Routing maps incoming HTTP requests to handler functions. Volter’s Router is a
tower::Service — it composes with any tower middleware.
Basic Routing
Create a router and attach handlers with .route():
#![allow(unused)]
fn main() {
use volter::{get, post, put, patch, delete, head, options, Router};
let app = Router::new()
.route("/", get(index))
.route("/users", get(list_users))
.route("/users", post(create_user));
}
The first argument is the path, the second is a MethodRouter created by one of
the free functions: get(), post(), put(), patch(), delete(), head(),
or options(). Unregistered HTTP methods produce a 405 Method Not Allowed
response.
Route Patterns
Static Paths
Exact path matching — the fastest option:
#![allow(unused)]
fn main() {
.route("/about", get(about_page))
.route("/contact", get(contact_page))
}
Parameterized Paths
Named parameters start with : and are captured by the Path extractor:
#![allow(unused)]
fn main() {
.route("/users/:id", get(user_by_id))
.route("/posts/:post_id/comments/:comment_id", get(comment_by_id))
}
The segment count must match exactly. /users/42 matches /users/:id;
/users/42/profile does not.
Method Routing
Each free function returns a MethodRouter that matches a single HTTP method:
#![allow(unused)]
fn main() {
use volter::{get, post, put, patch, delete, head, options, Router};
async fn list() -> &'static str { "list" }
async fn create() -> &'static str { "create" }
async fn update() -> &'static str { "updated" }
async fn remove() -> &'static str { "deleted" }
let app = Router::new()
.route("/items", get(list))
.route("/items", post(create))
.route("/items/:id", put(update))
.route("/items/:id", delete(remove));
}
A GET request to /items calls list; a POST request calls create;
a PUT request to /items/42 calls update; a DELETE request calls remove;
any other unregistered method returns 405.
Merging Routers
Combine independent routers with .merge():
#![allow(unused)]
fn main() {
let users_routes = Router::new()
.route("/users", get(list_users));
let posts_routes = Router::new()
.route("/posts", get(list_posts));
let app = users_routes.merge(posts_routes);
}
When two routers define the same path and method, the last merged wins.
Nesting Routers
Mount a router under a path prefix with .nest():
#![allow(unused)]
fn main() {
let api = Router::new()
.route("/users", get(list_users))
.route("/posts", get(list_posts));
let app = Router::new()
.nest("/api/v1", api);
}
This serves list_users at GET /api/v1/users and list_posts at
GET /api/v1/posts. The prefix is matched segment-by-segment: /api/v1
matches /api/v1/users but not /api/v2.
Each nested router preserves its own state and middleware.
Route Attribute Macros
As an alternative to the free-function API, you can annotate handlers with
#[get("/")], #[post("/")], #[put("/")], #[patch("/")],
#[delete("/")], #[head("/")], or #[options("/")]:
#![allow(unused)]
fn main() {
use volter::*;
#[get("/")]
async fn index() -> &'static str {
"Hello!"
}
let app = Router::new().route_attr(INDEX_ROUTE, index);
}
See the Route Attribute Macros chapter for details.
Route Matching Order
- Post-layer static routes — O(1) hashmap lookup
- Post-layer parameterized routes — linear scan
- Post-layer nested routers — prefix strip, then delegate
- Pre-layer (layered) service — routes wrapped by
.layer() - 404 Not Found — nothing matched