rust 64 lines · 8 steps

Composing Axum apps from feature modules

A FeatureModule trait lets each domain own its routes, then build_app nests them under name-based prefixes into one router.

Explained by highlit
1use axum::{
2 extract::{Path, State},
3 routing::{get, post},
4 Json, Router,
5};
6use serde::Serialize;
7 
8pub trait FeatureModule {
9 fn name(&self) -> &'static str;
10 fn router(&self) -> Router<AppState>;
11}
12 
13#[derive(Clone)]
14pub struct AppState {
15 pub db: sqlx::PgPool,
16}
17 
18pub struct BillingModule;
19 
20impl FeatureModule for BillingModule {
21 fn name(&self) -> &'static str {
22 "billing"
23 }
24 
25 fn router(&self) -> Router<AppState> {
26 Router::new()
27 .route("/invoices", get(list_invoices))
28 .route("/invoices/{id}/refund", post(refund_invoice))
29 }
30}
31 
32#[derive(Serialize)]
33struct Invoice {
34 id: i64,
35 total_cents: i64,
36}
37 
38async fn list_invoices(State(state): State<AppState>) -> Json<Vec<Invoice>> {
39 let rows = sqlx::query_as!(Invoice, "SELECT id, total_cents FROM invoices ORDER BY id DESC")
40 .fetch_all(&state.db)
41 .await
42 .unwrap_or_default();
43 Json(rows)
44}
45 
46async fn refund_invoice(State(state): State<AppState>, Path(id): Path<i64>) -> Json<Invoice> {
47 let row = sqlx::query_as!(
48 Invoice,
49 "UPDATE invoices SET refunded_at = now() WHERE id = $1 RETURNING id, total_cents",
50 id
51 )
52 .fetch_one(&state.db)
53 .await
54 .expect("invoice not found");
55 Json(row)
56}
57 
58pub fn build_app(state: AppState, modules: Vec<Box<dyn FeatureModule>>) -> Router {
59 let mut app = Router::new();
60 for module in modules {
61 app = app.nest(&format!("/{}", module.name()), module.router());
62 }
63 app.with_state(state)
64}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A trait over routers lets each feature package its endpoints and be assembled generically.
  2. 2Sharing state via Axum's State extractor keeps handlers decoupled from how the app is wired together.
  3. 3Nesting routers under a name prefix turns a Vec of boxed modules into a single mountable app.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Composing Axum apps from feature modules — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code