Defining Routes
When your application starts, the Leptos router matches the current URL to the routes that you've defined. The routes, in turn, are responsible for loading data, and setting up application state.
Basic Routes
Let's generate a new route:
$ auric generate route invoices
created src/routes/invoices_route.rs
updated src/routes/mod.rs
created src/templates/invoices.hbs
==> make
Compiling my-app v0.1.0 (/Users/davidr/workspaces/my-app)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.73s
This will generate the following file:
// src/routes/invoices_route.rs
use anyhow::Result;
use auric_runtime::async_trait::async_trait;
use auric_runtime::routing::Route;
use auric_runtime::{AuricRoute, Context};
use serde_json::{Value, json};
#[derive(AuricRoute, Default)]
#[route("/invoices")]
pub struct InvoicesRoute {}
#[async_trait]
impl Route for InvoicesRoute {
fn init(&mut self, _context: &dyn Context) -> Result<()> {
Ok(())
}
async fn model(&self, _params: RouteParams) -> Result<RouteModel> {
Ok(json!({}).into())
}
}
#[cfg(test)]
mod tests {
use super::InvoicesRoute;
#[test]
fn can_construct() {
let _ = InvoicesRoute::default();
}
}
The Auric build process also generates an ApplicationView that includes the Leptos Router and its
Route definitions. Let's have a look:
// src/generated/views/application.rs
...
#[component]
pub fn ApplicationView() -> impl IntoView {
view! {
<Router>
<Routes fallback=|| "Not found.">
<Route path=path!("/") view=IndexView />
<Route path=path!("/invoices") view=InvoicesView />
<Route path=path!("/*any") view=|| view! { <h1>Not found.</h1> } />
</Routes>
<h1>Welcome to Auric SPA!</h1>
<div />
</Router>
}
}
Nested Routes
Nested routes are used when you want to have a template that displays within another template. They are automatically generated for you when you create a hierarchy of routes such as this:
$ auric generate route customers/new
created src/routes/customers/new_route.rs
updated src/routes/customers/mod.rs
updated src/routes/mod.rs
created src/templates/customers/new.hbs
==> make
Compiling auric-runtime v0.1.8 (/Users/davidr/workspaces/auric-spa/auric/crates/auric-runtime)
Compiling my-app v0.1.0 (/Users/davidr/workspaces/my-app)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.93s
Examine the generated application view to see the nested route definitions:
$ cat src/generated/views/application.rs
...
#[component]
pub fn ApplicationView() -> impl IntoView {
...
view! {
<Router>
<Routes fallback=|| "Not found.">
<Route path=path!("/") view=IndexView />
<ParentRoute path=path!("/customers") view=CustomersView>
<Route path=path!("/new") view=CustomersNewView />
</ParentRoute>
<Route path=path!("/*any") view=|| view! { <h1>Not found.</h1> } />
</Routes>
...
</Router>
}
}
Index Routes
At every level of nesting, Auric automatically provides an implicit route for the index path.
Implicit routes use the pre-defined view=DefaultView from auric_runtime::components.
You can examine the generated application view for the details:
$ cat src/generated/views/application.rs
...
#[component]
pub fn ApplicationView() -> impl IntoView {
...
view! {
<Router>
<h1>"Welcome to Auric SPA!"</h1>
<div />
<Routes fallback=|| "Not found.">
<Route path=path!("/") view=IndexView />
<Route path=path!("/customer/:customer_id") view=CustomerView />
<ParentRoute path=path!("/customers") view=CustomersView>
<Route path=path!("") view=DefaultView /> <== IMPLICIT ROUTE
<Route path=path!("/new") view=CustomersNewView />
</ParentRoute>
<Route path=path!("/*any") view=|| view! { <h1>Not found.</h1> } />
</Routes>
</Router>
}
}
You can also define an explicit index route, to control the model() function or for other purposes:
$ auric generate route customers/index
created src/routes/customers/index_route.rs
created src/templates/customers/index.hbs
==> make
Compiling auric-build v0.2.0 (/Users/davidr/workspaces/auric-spa/auric/crates/auric-build)
Compiling auric-runtime v0.2.0 (/Users/davidr/workspaces/auric-spa/auric/crates/auric-runtime)
Compiling my-app v0.1.0 (/Users/davidr/workspaces/my-app)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.29s
$ cat src/generated/views/application.rs
...
#[component]
pub fn ApplicationView() -> impl IntoView {
...
view! {
<Router>
<h1>"Welcome to Auric SPA!"</h1>
<div />
<Routes fallback=|| "Not found.">
<Route path=path!("/") view=IndexView />
<Route path=path!("/customer/:customer_id") view=CustomerView />
<ParentRoute path=path!("/customers") view=CustomersView>
<Route path=path!("") view=CustomersIndexView /> <== EXPLICIT ROUTE
<Route path=path!("/new") view=CustomersNewView />
</ParentRoute>
<Route path=path!("/*any") view=|| view! { <h1>Not found.</h1> } />
</Routes>
</Router>
}
}
Dynamic Segments
One of the responsibilities of a route handler is to load a model. The route handler struct
needs to define the necessary URL parameter within the #[route] attribute macro.
This example shows a route that loads a customer record:
// src/routes/customer_route.rs
use crate::generated::Bucket;
use crate::models::customer::Customer;
use anyhow::Result;
use auric_runtime::data::Store;
use auric_runtime::routing::{Route, RouteModel, RouteParams};
use auric_runtime::{AuricRoute, Context, async_trait};
use jsonapi_core::Resource;
use std::sync::Arc;
#[derive(AuricRoute, Default)]
#[route("/customer/:customer_id")]
pub struct CustomerRoute {
store: Arc<Store>,
}
#[async_trait(?Send)]
impl Route for CustomerRoute {
fn init(&mut self, context: &dyn Context) -> Result<()> {
self.store = context.store();
Ok(())
}
async fn model(&self, params: RouteParams) -> Result<RouteModel> {
let id = params.get("customer_id").expect("Expected customer_id in params");
let customer: Customer = self.store.find_record(Bucket::Customers, id, None).await?;
Ok(Resource::from_typed(&customer)?.into())
}
}