Les 3 · Module 1 · Async en Axum-basis
Routing en handlers
Structureer de routes van kassa-api: methodes, geneste routers, fallback, statuscodes en responses die je zelf samenstelt met IntoResponse.
🎯 Na deze les kun je
- routes met verschillende HTTP-methodes en paden definiëren
- routers nesten en samenvoegen in modules
- statuscodes, headers en een fallback-handler teruggeven
- je eigen type IntoResponse laten implementeren
kassa-api krijgt nu echte resources: producten en bestellingen. In deze les leg je de routes vast en leer je hoe handlers verschillende antwoorden teruggeven. Data bewaar je nog even niet; dat komt in les 6.
Methodes op één pad
use axum::{routing::{get, post, put, delete}, Router};
let producten = Router::new()
.route("/", get(lijst_producten).post(maak_product))
.route("/{id}", get(haal_product).put(wijzig_product).delete(verwijder_product));
Een pad krijgt één route-aanroep; methodes koppel je met .get().post()…. Twee keer .route("/{id}", …) voor hetzelfde pad geeft
een panic bij het opstarten (Overlapping method route).
Nesten en modules
// src/main.rs
mod routes;
let app = Router::new()
.route("/health", get(|| async { "ok" }))
.nest("/v1/producten", routes::producten::router())
.nest("/v1/bestellingen", routes::bestellingen::router())
.fallback(niet_gevonden);
// src/routes/producten.rs
pub fn router() -> Router {
Router::new().route("/", get(lijst)).route("/{id}", get(haal))
}
Elke module levert een Router; main stelt ze samen. Zo blijft main.rs kort, ook als de service groeit.
Responses samenstellen
Alles wat IntoResponse is, mag een handler teruggeven. Handige vormen:
use axum::{http::{StatusCode, header}, response::{IntoResponse, Response}, Json};
async fn maak_product() -> impl IntoResponse {
(StatusCode::CREATED, [(header::LOCATION, "/v1/producten/7")], Json(serde_json::json!({ "id": 7 })))
}
async fn verwijder_product() -> StatusCode { StatusCode::NO_CONTENT }
async fn niet_gevonden() -> (StatusCode, &'static str) { (StatusCode::NOT_FOUND, "Deze route bestaat niet") }
Een tupel (StatusCode, headers, body) wordt in die volgorde toegepast.
Een eigen response-type
#[derive(serde::Serialize)]
struct Product { id: u32, naam: String, prijs_centen: u32 }
enum ProductAntwoord { Gevonden(Product), Onbekend }
impl IntoResponse for ProductAntwoord {
fn into_response(self) -> Response {
match self {
ProductAntwoord::Gevonden(p) => Json(p).into_response(),
ProductAntwoord::Onbekend => (StatusCode::NOT_FOUND, "Product niet gevonden").into_response(),
}
}
}
Voorspel eerst: je hebt .route("/v1/producten/{id}", get(haal)) en .route("/v1/producten/nieuw", get(formulier)). Welke handler
krijgt GET /v1/producten/nieuw?
Uitkomst
formulier. Axums router (matchit) geeft een vast segment voorrang boven een parameter, ongeacht de volgorde waarin je ze toevoegt.
/v1/producten/12 gaat naar haal. Is id een u32, dan geeft /v1/producten/abc een 400, want het pad matcht wel maar de extractor faalt.
Methode niet toegestaan
Doe je PATCH /v1/producten/1 terwijl alleen GET/PUT/DELETE bestaan, dan geeft Axum zelf 405 Method Not Allowed met een
Allow-header. De fallback is alleen voor paden die helemaal niet bestaan.
Typische fouten
Overlapping method route: hetzelfde pad twee keer met.route. Voeg de methodes samen.nestmet een pad dat eindigt op/:nest("/v1/producten/", …)is ongeldig; laat de slash weg.- Verschillende return-types in
if-takken: gebruik.into_response()in elke tak en returnResponse, of een eigen enum.
Oefenen
- Maak de modules
routes/producten.rsenroutes/bestellingen.rsmet lege handlers die tijdelijkStatusCode::NOT_IMPLEMENTEDteruggeven. - Laat
POST /v1/producten201 met eenLocation-header teruggeven. - Voeg een fallback toe die JSON
{"fout":"niet gevonden","pad":"..."}geeft (tip: extractoraxum::http::Uri). - Implementeer
ProductAntwoorden laatGET /v1/producten/{id}voor id 1 een vast product en anders 404 geven.
Modelantwoord oefening 1
// src/routes/mod.rs
pub mod producten;
pub mod bestellingen;
// src/routes/bestellingen.rs
use axum::{http::StatusCode, routing::get, Router};
pub fn router() -> Router {
Router::new().route("/", get(|| async { StatusCode::NOT_IMPLEMENTED }).post(|| async { StatusCode::NOT_IMPLEMENTED }))
}
Modelantwoord oefening 2
Zie maak_product in Responses samenstellen. Controleer met curl -i -X POST localhost:3000/v1/producten.
Modelantwoord oefening 3
async fn niet_gevonden(uri: axum::http::Uri) -> (StatusCode, Json<serde_json::Value>) {
(StatusCode::NOT_FOUND, Json(serde_json::json!({ "fout": "niet gevonden", "pad": uri.path() })))
}
Modelantwoord oefening 4
async fn haal(Path(id): Path<u32>) -> ProductAntwoord {
if id == 1 { ProductAntwoord::Gevonden(Product { id: 1, naam: "Koffie".into(), prijs_centen: 280 }) }
else { ProductAntwoord::Onbekend }
}
Lokaal oefenen
Geen live lab. Test elke route met curl -i zodat je de statuscode en headers ziet.
Samenvatting
- Eén
routeper pad, methodes samenvoegen;nesten modules voor structuur. - Responses: tupels met statuscode en headers, of een eigen
IntoResponse. - Axum geeft zelf 405 en 400; de fallback vangt onbekende paden.
- Volgende les: extractors voor pad, query en JSON.