Skip to main content

Router

Struct Router 

Source
pub struct Router { /* private fields */ }
Expand description

HTTP router for managing routes, middleware, and request dispatching.

The Router is the central component for routing HTTP requests to appropriate handlers. It supports dynamic path parameters, middleware chains, plugin integration, and global state management. Routes are matched based on HTTP method and path pattern, with support for trailing slash redirection and parameter extraction.

§Examples

use tako::{router::Router, Method, responder::Responder, types::Request};

async fn index(_req: Request) -> impl Responder {
    "Welcome to the home page!"
}

async fn user_profile(_req: Request) -> impl Responder {
    "User profile page"
}

let mut router = Router::new();
router.route(Method::GET, "/", index);
router.route(Method::GET, "/users/{id}", user_profile);
router.state("app_name", "MyApp".to_string());

Implementations§

Source§

impl Router

Source

pub fn new() -> Router

Creates a new, empty router.

Source

pub fn route<H, T>( &mut self, method: Method, path: &str, handler: H, ) -> Arc<Route>
where H: Handler<T> + Clone + 'static,

Registers a new route with the router.

Associates an HTTP method and path pattern with a handler function. The path can contain dynamic segments using curly braces (e.g., /users/{id}), which are extracted as parameters during request processing.

§Panics

Panics if a route with the same method and path pattern is already registered.

§Examples
use tako::{router::Router, Method, responder::Responder, types::Request};

async fn get_user(_req: Request) -> impl Responder {
    "User details"
}

async fn create_user(_req: Request) -> impl Responder {
    "User created"
}

let mut router = Router::new();
router.route(Method::GET, "/users/{id}", get_user);
router.route(Method::POST, "/users", create_user);
router.route(Method::GET, "/health", |_req| async { "OK" });
Source

pub fn get<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
where H: Handler<T> + Clone + 'static,

Registers a GET route. Shorthand for Router::route with Method::GET.

Source

pub fn post<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
where H: Handler<T> + Clone + 'static,

Registers a POST route. Shorthand for Router::route with Method::POST.

Source

pub fn put<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
where H: Handler<T> + Clone + 'static,

Registers a PUT route. Shorthand for Router::route with Method::PUT.

Source

pub fn delete<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
where H: Handler<T> + Clone + 'static,

Registers a DELETE route. Shorthand for Router::route with Method::DELETE.

Source

pub fn patch<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
where H: Handler<T> + Clone + 'static,

Registers a PATCH route. Shorthand for Router::route with Method::PATCH.

Source

pub fn head<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
where H: Handler<T> + Clone + 'static,

Registers a HEAD route. Shorthand for Router::route with Method::HEAD.

Source

pub fn options<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
where H: Handler<T> + Clone + 'static,

Registers an OPTIONS route. Shorthand for Router::route with Method::OPTIONS.

Source

pub fn mount_all(&mut self) -> &mut Router

Registers every route declared via the #[tako::route] / #[tako::get] (and friends) attribute macros into this router.

Each macro contributes a thunk into the global TAKO_ROUTES slice at link time; this method walks the slice and invokes each thunk against self, which calls Router::route under the hood. Routes are registered in the order the linker emits them — typically the order they appear within a translation unit, but unspecified across crates. If two thunks register the same (method, path) pair, the second call will panic, matching the behavior of Router::route.

§Why linkme and not explicit registration

We keep the linkme distributed-slice strategy on purpose. The alternative — an explicit register_routes!(my_crate::routes) invocation per crate — was considered and rejected because:

  • Adding a handler would require touching three places (the handler itself, the per-crate registration list, and the call site that imports it) instead of one. The macro authoring story is the main reason teams pick attribute routing in the first place.
  • Cross-crate path collisions panic at startup either way; explicit registration does not buy any extra safety.
  • Link-order non-determinism only matters when two routes share a (method, path) pair — that is already a hard failure and a CI test catches it deterministically.
  • Prefix grouping is already covered by Router::mount_all_into, so “I want all my routes under /api” does not require explicit registration.

Callers that need stable, deterministic ordering should call Router::route directly.

§Examples
use tako::{get, router::Router};

#[get("/health")]
async fn health() -> impl tako::responder::Responder { "ok" }

let mut router = Router::new();
router.mount_all();
Source

pub fn mount_all_into(&mut self, prefix: &str) -> &mut Router

Like Router::mount_all but registers every macro-declared route under the given path prefix. The prefix is normalized (trailing / stripped), then prepended to each registered path. Useful when you want, e.g., all #[get("/users")] declarations to live under /api.

Ordering across crates remains the linker’s choice (see Router::mount_all for details).

§Examples
let mut router = Router::new();
router.mount_all_into("/api"); // /users → /api/users, /health → /api/health
Source

pub fn scope<F>(&mut self, prefix: &str, build: F) -> &mut Router
where F: FnOnce(&mut Router),

Registers a group of routes under a shared path prefix.

The closure receives self with the prefix active, so any route() / get() / post() etc. calls inside register the routes with the prefix prepended. Prefixes nest: a scope("/v1", |r| r.scope("/users", …)) produces routes under /v1/users. Cold path; no dispatch impact.

§Examples
use tako::router::Router;
use tako::responder::Responder;

async fn list_users() -> impl Responder { "users" }
async fn create_user() -> impl Responder { "created" }

let mut router = Router::new();
router.scope("/api/v1", |r| {
    r.get("/users", list_users);
    r.post("/users", create_user);
});
Source

pub fn nest(&mut self, prefix: &str, child: Router) -> &mut Router

Mounts every route from a child router under the given path prefix.

Unlike Router::merge, nest builds new Arc<Route> instances for each child route via Route::cloned_with_path — so re-nesting the same child cannot double-stack its global middleware onto the same shared Arc<Route>. The child router’s global middleware chain is prepended to each newly-registered route’s middleware chain (so child globals run before child-route middleware at dispatch time).

Caveats:

  • Route-level plugins on the child are not carried over.
  • The child’s fallback / error handlers are not inherited.
§Panics

Panics at registration time if mounting the child would conflict with a route already present on self (same method + same prefixed path). Mirrors the behavior of Router::route — route registration is a startup-time operation and conflicts are configuration bugs, not runtime conditions.

§Examples
use tako::router::Router;
use tako::responder::Responder;

async fn list_users() -> impl Responder { "users" }

let mut api = Router::new();
api.get("/users", list_users);

let mut root = Router::new();
root.nest("/api/v1", api); // /users → /api/v1/users
Source

pub fn route_with_tsr<H, T>( &mut self, method: Method, path: &str, handler: H, ) -> Arc<Route>
where H: Handler<T> + Clone + 'static,

Registers a route with trailing slash redirection enabled.

When TSR is enabled, requests to paths with or without trailing slashes are automatically redirected to the canonical version. This helps maintain consistent URLs and prevents duplicate content issues.

§Panics
  • Panics if called with the root path ("/") since TSR is not applicable.
  • Panics if a route with the same method and path pattern is already registered.
§Examples
use tako::{router::Router, Method, responder::Responder, types::Request};

async fn api_handler(_req: Request) -> impl Responder {
    "API endpoint"
}

let mut router = Router::new();
// Both "/api" and "/api/" will redirect to the canonical form
router.route_with_tsr(Method::GET, "/api", api_handler);
Source

pub async fn dispatch(&self, req: Request<TakoBody>) -> Response<TakoBody>

Dispatches an incoming request to the appropriate route handler.

Source

pub fn state<T>(&mut self, value: T)
where T: Clone + Send + Sync + 'static,

Adds a value to the global type-based state accessible by all handlers.

Global state allows sharing data across different routes and middleware. Values are stored by their concrete type and retrieved via the State extractor (from tako-extractors) or with crate::state::get_state.

§Examples
use tako::router::Router;

#[derive(Clone)]
struct AppConfig { database_url: String, api_key: String }

let mut router = Router::new();
router.state(AppConfig {
    database_url: "postgresql://localhost/mydb".to_string(),
    api_key: "secret-key".to_string(),
});
// You can also store simple types by type:
router.state::<String>("1.0.0".to_string());
Source

pub fn with_state<T>(&mut self, value: T) -> &mut Router
where T: Clone + Send + Sync + 'static,

Inserts a value into this router’s instance-local typed state.

Unlike Router::state (which writes the process-global registry and therefore allows only one value per T per process), with_state is per-router — multiple routers can hold distinct Ts without collisions.

The State extractor (from tako-extractors) reads the per-router store first and falls back to the global store if no per-router value exists, so existing code that uses set_state / Router::state continues to work unchanged.

Hot-path cost is one Arc clone per request only when at least one with_state call has happened on this router; an AtomicBool::Acquire fast-path skips it for routers that don’t use instance-local state.

§Examples
use tako::router::Router;

#[derive(Clone)]
struct Db;

let mut router = Router::new();
router.with_state(Db);
Source

pub fn router_state(&self) -> &Arc<RouterState>

Returns the per-router typed state (shared Arc).

Source

pub fn signals(&self) -> &SignalArbiter

Available on crate feature signals only.

Returns a reference to the signal arbiter.

Source

pub fn signal_arbiter(&self) -> SignalArbiter

Available on crate feature signals only.

Returns a clone of the signal arbiter, useful for sharing through state.

Source

pub fn on_signal<F, Fut>(&self, id: impl Into<String>, handler: F)
where F: Fn(Signal) -> Fut + Send + Sync + 'static, Fut: Future<Output = ()> + Send + 'static,

Available on crate feature signals only.

Registers a handler for a named signal on this router’s arbiter.

Source

pub async fn emit_signal(&self, signal: Signal)

Available on crate feature signals only.

Emits a signal through this router’s arbiter.

Source

pub fn middleware<F, Fut, R>(&self, f: F) -> &Router
where F: Fn(Request<TakoBody>, Next) -> Fut + Clone + Send + Sync + 'static, Fut: Future<Output = R> + Send + 'static, R: Responder + Send + 'static,

Adds global middleware to the router.

Global middleware is executed for all routes in the order it was added, before any route-specific middleware. Middleware can modify requests, generate responses, or perform side effects like logging or authentication.

§Examples
use tako::{router::Router, middleware::Next, types::Request};

let mut router = Router::new();

// Logging middleware
router.middleware(|req, next| async move {
    println!("Request: {} {}", req.method(), req.uri());
    let response = next.run(req).await;
    println!("Response: {}", response.status());
    response
});

// Authentication middleware
router.middleware(|req, next| async move {
    if req.headers().contains_key("authorization") {
        next.run(req).await
    } else {
        "Unauthorized".into_response()
    }
});
Source

pub fn fallback<F, Fut, R>(&mut self, handler: F) -> &mut Router
where F: Fn(Request<TakoBody>) -> Fut + Clone + Send + Sync + 'static, Fut: Future<Output = R> + Send + 'static, R: Responder + Send + 'static,

Sets a fallback handler that will be executed when no route matches.

The fallback runs after global middlewares and can be used to implement custom 404 pages, catch-all logic, or method-independent handlers.

§Examples
use tako::{router::Router, Method, responder::Responder, types::Request};

async fn not_found(_req: Request) -> impl Responder { "Not Found" }

let mut router = Router::new();
router.route(Method::GET, "/", |_req| async { "Hello" });
router.fallback(not_found);
Source

pub fn fallback_with_extractors<H, T>(&mut self, handler: H) -> &mut Router
where H: Handler<T> + Clone + 'static,

Sets a fallback handler that supports extractors (like Path, Query, etc.).

Use this when your fallback needs to parse request data via extractors. If you only need access to the raw Request, prefer fallback for simpler type inference.

§Examples
use tako::{router::Router, responder::Responder, extractors::{path::Path, query::Query}};

#[derive(serde::Deserialize)]
struct Q { q: Option<String> }

async fn fallback_with_q(Path(_p): Path<String>, Query(_q): Query<Q>) -> impl Responder {
    "Not Found"
}

let mut router = Router::new();
router.fallback_with_extractors(fallback_with_q);
Source

pub fn timeout(&mut self, duration: Duration) -> &mut Router

Sets a default timeout for all routes.

This timeout can be overridden on individual routes using Route::timeout. When a request exceeds the timeout duration, the timeout fallback handler is invoked (if configured) or a 408 Request Timeout response is returned.

§Examples
use tako::router::Router;
use std::time::Duration;

let mut router = Router::new();
router.timeout(Duration::from_secs(30));
Source

pub fn timeout_fallback<F, Fut, R>(&mut self, handler: F) -> &mut Router
where F: Fn(Request<TakoBody>) -> Fut + Clone + Send + Sync + 'static, Fut: Future<Output = R> + Send + 'static, R: Responder + Send + 'static,

Sets a fallback handler that will be executed when a request times out.

If no timeout fallback is set, a default 408 Request Timeout response is returned.

§Examples
use tako::{router::Router, responder::Responder, types::Request};
use std::time::Duration;

async fn timeout_handler(_req: Request) -> impl Responder {
    "Request took too long"
}

let mut router = Router::new();
router.timeout(Duration::from_secs(30));
router.timeout_fallback(timeout_handler);
Source

pub fn error_handler( &mut self, handler: impl Fn(Response<TakoBody>) -> Response<TakoBody> + Send + Sync + 'static, ) -> &mut Router

Sets a global error handler for 5xx responses.

The error handler receives any response with a server error status and can transform it (e.g., to return JSON-formatted errors instead of plain text).

§Examples
use tako::router::Router;
use tako::body::TakoBody;

let mut router = Router::new();
router.error_handler(|resp| {
    let status = resp.status();
    let body = format!(r#"{{"error": "{}"}}"#, status.canonical_reason().unwrap_or("Unknown"));
    let mut res = http::Response::new(TakoBody::from(body));
    *res.status_mut() = status;
    res.headers_mut().insert(
        http::header::CONTENT_TYPE,
        http::HeaderValue::from_static("application/json"),
    );
    res
});
Source

pub fn client_error_handler( &mut self, handler: impl Fn(Response<TakoBody>) -> Response<TakoBody> + Send + Sync + 'static, ) -> &mut Router

Sets a global error handler for 4xx responses.

Mirrors Router::error_handler but fires for client errors. Useful for converting bare 404 / 405 / 422 responses into structured error documents (e.g. via crate::problem::default_problem_responder).

Source

pub fn use_problem_json(&mut self) -> &mut Router

Convenience: install crate::problem::default_problem_responder for both 4xx and 5xx so unhandled errors always render as application/problem+json.

Source

pub fn plugin<P>(&mut self, plugin: P) -> &mut Router
where P: TakoPlugin + Clone + Send + Sync + 'static,

Available on crate feature plugins only.

Registers a plugin with the router.

Plugins extend the router’s functionality by providing additional features like compression, CORS handling, rate limiting, or custom behavior. Plugins are initialized once when the server starts.

§Examples
use tako::{router::Router, plugins::TakoPlugin};
use anyhow::Result;

struct LoggingPlugin;

impl TakoPlugin for LoggingPlugin {
    fn name(&self) -> &'static str {
        "logging"
    }

    fn setup(&self, _router: &Router) -> Result<()> {
        println!("Logging plugin initialized");
        Ok(())
    }
}

let mut router = Router::new();
router.plugin(LoggingPlugin);
Source

pub fn merge(&mut self, other: Router)

Merges another router into this router.

This method combines routes and middleware from another router into the current one. Routes are copied over, and the other router’s global middleware is prepended to each merged route’s middleware chain.

§Panics

Panics at registration time if a merged route conflicts with one already present on self (same method + same path). Mirrors the behavior of Router::route and Router::nest — merge is a startup-time operation and route conflicts are configuration bugs.

§Examples
use tako::{router::Router, Method, responder::Responder, types::Request};

async fn api_handler(_req: Request) -> impl Responder {
    "API response"
}

async fn web_handler(_req: Request) -> impl Responder {
    "Web response"
}

// Create API router
let mut api_router = Router::new();
api_router.route(Method::GET, "/users", api_handler);
api_router.middleware(|req, next| async move {
    println!("API middleware");
    next.run(req).await
});

// Create main router and merge API router
let mut main_router = Router::new();
main_router.route(Method::GET, "/", web_handler);
main_router.merge(api_router);
Source

pub fn collect_openapi_routes(&self) -> Vec<(Method, String, RouteOpenApi)>

Available on crate features utoipa or vespera only.

Collects OpenAPI metadata from all registered routes.

Returns a vector of tuples containing the HTTP method, path, and OpenAPI metadata for each route that has OpenAPI information attached.

§Examples
use tako::{router::Router, Method};

let mut router = Router::new();
router.route(Method::GET, "/users", list_users)
    .summary("List users")
    .tag("users");

for (method, path, openapi) in router.collect_openapi_routes() {
    println!("{} {} - {:?}", method, path, openapi.summary);
}
Source

pub fn compact_routes(&mut self)

Drops dangling Weak<Route> entries from the per-method routes index.

All current routes stay live for the router’s lifetime, so this is a no-op in well-behaved code. It exists as a safety valve: if any future API ever removes from inner (hot reload, route deregistration), or if downstream code holds the Arc<Route> returned from Router::route past the router’s lifetime, this method bounds the size of the index.

Cold path; safe to call repeatedly. Linear in the total number of registered routes.

Trait Implementations§

Source§

impl Default for Router

Source§

fn default() -> Router

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl !Freeze for Router

§

impl !RefUnwindSafe for Router

§

impl Send for Router

§

impl Sync for Router

§

impl Unpin for Router

§

impl UnsafeUnpin for Router

§

impl !UnwindSafe for Router

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FutureExt for T

Source§

fn with_context(self, otel_cx: Context) -> WithContext<Self>

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
Source§

fn with_current_context(self) -> WithContext<Self>

Attaches the current Context to this type, returning a WithContext wrapper. Read more
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more