Routing Macros
Macro urlpatterns!
Define application routes with names for URL resolution:
use crate::views;
use runique::prelude::*;
use runique::{urlpatterns, view};
pub fn routes() -> Router {
urlpatterns! {
"/" => view!{ views::index }, name = "index",
"/users" => view! { views::user_list }, name = "users",
"/users/{id}" => view!{ views::user_detail }, name = "user_detail",
"/users/{id}/delete" => view!{ views::delete_user }, name = "user_delete",
}
}
Dynamic path segments use the
{param}syntax (Axum 0.8). The old:paramsyntax is no longer supported.
With names (recommended)
Names allow URL resolution in templates via {% link "name" %}:
urlpatterns! {
"/" => view!{ views::index }, name = "index",
"/users/{id}" => view!{ views::user_detail }, name = "user_detail",
}
<a href='{% link "index" %}'>Home</a>
<a href='{% link "user_detail" id="42" %}'>Profile</a>
Without names
urlpatterns! {
"/" => view!{ views::index },
"/about" => view!{ views::about },
}
Macro view!
A single handler handles GET and POST as well as PUT and DELETE (recommended pattern with request.is_get() / request.is_post()):
// In routes
"/inscription" => view!{ views::inscription }, name = "inscription",
// In handler
pub async fn inscription(mut request: Request) -> AppResult<Response> {
let mut form: RegisterForm = request.form();
if request.is_get() {
context_update!(request => { "form" => &form });
return request.render("form.html");
}
if request.is_post() {
if form.is_valid().await {
// ...
}
}
request.render("form.html")
}
Routing a specific method (GET / POST / PUT…)
view!{ handler } wires the same handler onto all five methods (GET, POST, PUT, DELETE, PATCH). This is the recommended pattern when a single handler serves the form on both GET and POST via request.is_get() / request.is_post().
When you want a distinct handler per method, don't use view!: pass the Axum combinators directly (get, post, put, delete, patch), already re-exported by the prelude. urlpatterns! accepts any expression that produces a MethodRouter as a handler:
use crate::views;
use runique::prelude::*; // get, post, put, delete, patch included
use runique::urlpatterns;
pub fn routes() -> Router {
urlpatterns! {
// Single handler, all methods (dispatch via request.is_get/is_post)
"/inscription" => view!{ views::inscription }, name = "inscription",
// Distinct handlers per method
"/login" => get(views::login_get).post(views::login_post), name = "login",
// Only one method allowed — others return 405
"/users/{id}" => delete(views::delete_user), name = "user_delete",
// Chaining several methods
"/articles/{id}" => get(views::article_get)
.put(views::article_update)
.delete(views::article_delete), name = "article",
}
}
With the Axum combinators, only the declared methods are accepted: any other method automatically returns
405 Method Not Allowed. By contrast,view!accepts all five methods — it's up to the handler to filter.
Per-method handlers share the same signature as any other Runique handler:
pub async fn login_get(mut request: Request) -> AppResult<Response> {
request.render("login.html")
}
pub async fn login_post(mut request: Request) -> AppResult<Response> {
let mut form: LoginForm = request.form();
if form.is_valid().await {
// ...
}
request.render("login.html")
}
Choosing the route type for the job
Each combinator explicitly fixes the accepted methods. A few typical cases:
urlpatterns! {
// Display-only page (GET) — a POST returns 405
"/about" => get(views::about), name = "about",
// Endpoint that only receives data (POST) — webhook, action
"/webhooks/stripe" => post(views::stripe_webhook), name = "stripe",
// Form: GET displays, POST processes — two separate handlers
"/contact" => get(views::contact_form)
.post(views::contact_submit), name = "contact",
// REST API on a resource
"/api/articles" => get(views::articles_index)
.post(views::articles_create), name = "articles",
"/api/articles/{id}" => get(views::article_show)
.put(views::article_update)
.patch(views::article_patch)
.delete(views::article_destroy), name = "article",
}
On the handler side, each method gets its own function, with no is_get() / is_post() check:
// GET /contact — render the empty form
pub async fn contact_form(mut request: Request) -> AppResult<Response> {
let form: ContactForm = request.form();
context_update!(request => { "contact_form" => &form });
request.render("contact.html")
}
// POST /contact — validate and process the submission
pub async fn contact_submit(mut request: Request) -> AppResult<Response> {
let mut form: ContactForm = request.form();
if form.is_valid().await {
// send the email, flash success, redirect…
return Ok(Redirect::to("/contact").into_response());
}
context_update!(request => { "contact_form" => &form });
request.render("contact.html")
}
Simple rule: use
view!when a single handler serves several methods (internal dispatch), and theget/post/… combinators when each method deserves its own handler or when you want to restrict the allowed methods.
Macro impl_objects! (bonus)
impl_objects! is automatically generated by the daemon in each entity file. It adds a Django-like manager usable directly in handlers:
use crate::entities::users;
let user = users::Entity::objects
.filter(users::Column::Username.eq("john"))
.first(&db)
.await?;
See the ORM guide for more details.