Beetje Coderen

Les 1 · Module 1 · Async en Axum-basis

Je eerste Axum-service

Een Axum-project opzetten en een eerste endpoint maken dat JSON teruggeeft.

⏱ 60–90 minuten leestijd + oefenen

🎯 Na deze les kun je

  • een Cargo-project met Axum en Tokio opzetten
  • een handler schrijven die tekst en JSON teruggeeft
  • uitleggen waarom main async is

In Een beetje Rust schreef je command-line tools. Een webservice is een programma dat blijft draaien en op verzoeken wacht. Dat wachten gebeurt asynchroon: terwijl het ene verzoek op de database wacht, kan het programma een ander verzoek afhandelen. Axum is een webframework bovenop de Tokio-runtime dat daar goed gebruik van maakt.

Wat je nodig hebt

  • Rust via rustup (cargo --version)
  • Kennis van ownership, structs, enums en Result uit Een beetje Rust

Project maken

cargo new kassa-api && cd kassa-api
cargo add axum
cargo add tokio --features full
cargo add serde --features derive

Uitgewerkt voorbeeld

src/main.rs:

use axum::{extract::Path, routing::get, Json, Router};
use serde::Serialize;

#[derive(Serialize)]
struct Totaal {
    bedrag: u32,
    btw: f64,
}

async fn home() -> &'static str {
    "Kassa"
}

async fn totaal(Path(bedrag): Path<u32>) -> Json<Totaal> {
    Json(Totaal { bedrag, btw: bedrag as f64 * 0.21 })
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(home))
        .route("/totaal/:bedrag", get(totaal));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    println!("Luistert op http://127.0.0.1:3000");
    axum::serve(listener, app).await.unwrap();
}

Let op: vanaf Axum 0.8 schrijf je padparameters als /totaal/{bedrag}. Kijk welke versie cargo add heeft geïnstalleerd (Cargo.toml) en gebruik de bijbehorende notatie.

Voorspel eerst: wat geeft curl http://127.0.0.1:3000/totaal/100 terug? En /totaal/-5?

cargo run
curl -i http://127.0.0.1:3000/totaal/100

Je krijgt {"bedrag":100,"btw":21.0} met content-type: application/json. Bij -5 past de waarde niet in een u32. Axum geeft dan zelf een 400 Bad Request terug, nog voordat je handler draait. Het type van je parameter is dus ook je eerste validatie.

Wat is er nieuw?

  • #[tokio::main] start de async-runtime; zonder runtime kan een async fn niet worden uitgevoerd.
  • Een handler is een gewone async fn. Wat hij teruggeeft, moet Axum kunnen omzetten naar een antwoord (&str, Json<T>, StatusCode, …).
  • Path(bedrag) is een extractor: Axum haalt het stuk uit de URL en zet het om naar het type dat je vraagt.

Oefenen

  1. Voeg /totaal/:bedrag/incl toe dat ook het bedrag inclusief btw teruggeeft.
  2. Geef bij bedrag 0 een StatusCode::BAD_REQUEST terug (tip: return type Result<Json<Totaal>, StatusCode>).
  3. Meet met time curl … hoe snel een antwoord is in cargo run --release tegenover debug.

Typische fouten

  • the trait Handler<_, _> is not implemented: meestal geeft je handler een type terug dat Axum niet kent, of is de functie niet async.
  • Versieverschillen: voorbeelden online gebruiken soms een oudere Axum-API (axum::Server). Volg de versie in je Cargo.toml.
  • Address already in use: er draait nog een vorige cargo run.

Samenvatting

Een Axum-service is een Router met handlers, gestart op de Tokio-runtime. In de volgende les kijk je wat async in Rust echt betekent en waarom dat voor een webservice uitmaakt.