Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

  1. Post-layer static routes — O(1) hashmap lookup
  2. Post-layer parameterized routes — linear scan
  3. Post-layer nested routers — prefix strip, then delegate
  4. Pre-layer (layered) service — routes wrapped by .layer()
  5. 404 Not Found — nothing matched