Router

rustdocs (main)

The trillium router is based on routefinder. This router supports two types of patterns: Untyped params and a single wildcard. Named params are captured in a map-like interface. Any handler can be mounted inside of a Router (including other Routers), allowing entire applications to be mounted on a path, and allowing for tuple handlers to be run on a given route. Any handler mounted inside of a route that includes a * will have the url rewritten to the contents of that star.

Alternative routers that are not based on routefinder are a prime opportunity for innovation and exploration.

Here's a simple example of an application that responds to a request like http://localhost:8000/greet/earth with "hello earth" and http://localhost:8000/greet/mars with "hello mars" and responds to http://localhost:8000 with "hello everyone"

use trillium::Conn;
use trillium_router::{Router, RouterConnExt};

pub fn main() {
    trillium_smol::run(
        Router::new()
            .get("/", |conn: Conn| async move { conn.ok("hello everyone") })
            .get("/hello/:planet", |conn: Conn| async move {
                let planet = conn.param("planet").unwrap();
                let response_body = format!("hello {planet}");
                conn.ok(response_body)
            }),
    );
}

Nesting

Trillium also supports nesting routers, making it possible to express complex sub-applications, vaguely along the lines of a rails engine. When there are additional types of routers, it will be possible for an application built with one type of router to be published as a crate and nested inside of another router as long as they depend on a compatible version of the trillium crate.

use trillium::{conn_try, conn_unwrap, Conn, Handler};
use trillium_logger::Logger;
use trillium_router::{Router, RouterConnExt};

struct User {
    id: usize,
}

mod nested_app {
    use super::*;
    async fn load_user(conn: Conn) -> Conn {
        let id = conn_try!(conn.param("user_id").unwrap().parse(), conn);
        let user = User { id }; // imagine we were loading a user from a database here
        conn.with_state(user)
    }

    async fn greeting(mut conn: Conn) -> Conn {
        let user = conn_unwrap!(conn.take_state::<User>(), conn);
        conn.ok(format!("hello user {}", user.id))
    }

    async fn post(mut conn: Conn) -> Conn {
        let user = conn_unwrap!(conn.take_state::<User>(), conn);
        let body = conn_try!(conn.request_body_string().await, conn);
        conn.ok(format!("hello user {}, {}", user.id, body))
    }

    async fn some_other_route(conn: Conn) -> Conn {
        conn.ok("this is an uninspired example")
    }

    pub fn handler() -> impl Handler {
        (
            load_user,
            Router::new()
                .get("/greeting", greeting)
                .get("/some/other/route", some_other_route)
                .post("/post", post),
        )
    }
}
pub fn main() {
    env_logger::init();
    trillium_smol::run((
        Logger::new(),
        Router::new()
            .get("/", |conn: Conn| async move { conn.ok("hello everyone") })
            .any(&["get", "post"], "/users/:user_id/*", nested_app::handler()),
    ));
}