This version is in beta. Some features may change before release.

Routes

Declare your app's URL patterns once with the Routes builder - handlers and the dev-mode 404 panel stay in sync without parallel declarations.

Routes is umbral's URL-patterns builder. Each .get(...), .post(...), etc. call registers the handler with the axum router AND records the path in the framework's route registry in the same step. The dev-mode 404 page reads that registry, so the URL list it shows always matches what's actually wired in. No parallel path_list to drift out of sync.

The basic shape

Code
rust
use umbral::prelude::*;
 
App::builder()
.routes(
Routes::new()
.get("/", home)
.get("/articles", list_articles_html)
.get("/articles/{id}", article_detail)
.post("/api/articles", create_article)
.delete("/api/articles/{id}", destroy_article),
)
.build()?;

That single block does two things at once: every method call adds the handler to axum's router and pushes a RouteSpec { path, methods: ["GET"] } into the registry the dev 404 panel renders.

Per-method shorthand

One method per call, with the framework wrapping your handler in the right axum::routing::<method>(...):

MethodRoutes call
GET.get(path, handler)
POST.post(path, handler)
PUT.put(path, handler)
PATCH.patch(path, handler)
DELETE.delete(path, handler)
HEAD.head(path, handler)
OPTIONS.options(path, handler)

handler is any axum Handler<T, ()> - async fn returning anything that IntoResponses.

Per-route middleware: .layered

When you need middleware (auth gating, rate-limiting, per-route timeouts) on one route only, build a MethodRouter with .layer(...) and pass it via .layered(method, path, mr):

Code
rust
use axum::routing::get;
use umbral_auth::login_required::login_required_html;
 
Routes::new()
.get("/", home)
.layered(
"GET",
"/dashboard",
get(dashboard).layer(login_required_html("/login")),
)
Info

The gotcha .layered protects you from: axum::Router::new().route(...).layer(L) applies L to every route on that Router instance. MethodRouter::layer(L) scopes to just that path. Routes::layered accepts a MethodRouter so layers attach where you expect.

Multi-method on one path: .route

When two methods share the same path (e.g. a collection endpoint accepting both GET and POST):

Code
rust
use axum::routing::{get, post};
 
Routes::new().route(
&["GET", "POST"],
"/api/comments",
get(list_comments).post(create_comment),
)

The methods slice is what shows up as method badges on the dev 404 panel; the chained MethodRouter is what axum dispatches against.

Escape hatch for axum power-users: .with_router

For features the per-method shorthands don't expose - typed State, nest, fallback, custom middleware stacks - build a plain axum::Router and merge it in:

Code
rust
use axum::Router;
use axum::routing::get;
 
Routes::new()
.get("/", home)
.with_router(
Router::new()
.nest("/api", api_router())
.fallback(custom_404_json),
)

Paths inside the external router contribute their handlers but don't appear in the framework's route registry - axum doesn't expose its internal route table, so the dev 404 panel can't see them. To surface them, declare the same paths through the per-method shorthands on a Routes builder (or a plugin's route_paths()) so they land in the registry.

See also

  • Auth gating - the login_required_* layers commonly used with .layered
  • Trailing slash - SlashRedirect policy
  • Error pages - not_found_template / server_error_template (the dev 404 panel lives in the default 404 template)
webrouting