Thuta Learning
Rust
ProjectsProgrammingbeginner

Project — Axum API Endpoint

What you'll walk away with

  • Explain the core ideas behind Project — Axum API Endpoint
  • Run the sample Rust code and verify its output
  • Apply the technique correctly to the Tutorial Platform and production scenarios

Build the mental model

Axum is a Rust web framework built on the `tokio` runtime — a direct application of Lesson 19's async fundamentals into a production-shaped HTTP server, where each route handler is an `async fn` capable of concurrently handling hundreds of requests with just a handful of OS threads. Axum's core design idea is the "extractor" pattern, declaring exactly which part of a request a handler needs (a path parameter, a query string, a JSON body) through the handler function's parameter types — `Path(id): Path<u32>` auto-extracts and parses `id` from the URL path, and if parsing fails, Axum automatically responds with 400 Bad Request, so no manual validation code is needed. To serialize a response as JSON, add `#[derive(Serialize)]` to a struct, auto-implementing `serde::Serialize` — a pattern that gets you Lesson 14's trait implementation via compiler-generated code (a derive macro) instead of hand-written boilerplate. The route/handler concept is similar to a framework like Node.js/Express, but Axum's handlers are type-checked — writing the wrong extractor type is a compile error, not a runtime one — showing exactly the shape in which this site's backend logic could be complemented by a more memory-safe, type-safe implementation.

Connect it to a real scenario

Build the Tutorial Platform's `GET /lessons/{id}` endpoint as an `async fn get_lesson(Path(id): Path<u32>) -> Json<LessonMetadata>` handler — `id` auto-parses from the URL path (Axum sends an automatic 400 response for an invalid id), look up the metadata from `LessonStat` data (Lesson 21's project), and return it serialized with a `Json(...)` wrapper. Define the router with `Router::new().route("/lessons/{id}", get(get_lesson))` and run the server with `axum::serve(listener, router).await` inside a `#[tokio::main]` main function — Lesson 19's tokio runtime setup returns here. When a lesson id isn't found (putting Lesson 10's `Option<T>` back into practice), explicitly return an HTTP 404 status code — unwrapping the `None` case blindly instead could panic the whole server thread.

Try the working example

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

#[derive(Serialize)]
struct LessonMetadata {
    id: u32,
    title: String,
    word_count: u32,
}

async fn get_lesson(Path(id): Path<u32>) -> Result<Json<LessonMetadata>, StatusCode> {
    let lessons = [
        LessonMetadata { id: 1, title: "Ownership".into(), word_count: 620 },
        LessonMetadata { id: 2, title: "Borrowing".into(), word_count: 540 },
    ];
    lessons
        .into_iter()
        .find(|lesson| lesson.id == id)
        .map(Json)
        .ok_or(StatusCode::NOT_FOUND)
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/lessons/{id}", get(get_lesson));
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
You should see
A `GET /lessons/1` request returns the JSON response `{"id":1,"title":"Ownership","word_count":620}`; requesting a missing id like `GET /lessons/99` returns an HTTP 404 status instead.

5-minute try-it

Add a route with a query parameter, `GET /lessons?difficulty=basic` — use the `Query<HashMap<String, String>>` extractor to read the `difficulty` value and return a matching lesson list (dummy data) as a JSON array.

One important caution

Calling `Option::unwrap()` when a lesson id isn't found in the data store and trying to return a Json response anyway — panicking on the None case can send the client an HTTP 500 (internal server error); an explicit 404 status should be returned instead.

Writing a handler function as `fn` (non-async) and then trying to call a database or file operation that needs `.await` — this is a compile error; every Axum handler must be `async fn`.

Axum DocumentationRust

Easy traps

  • Calling `Option::unwrap()` when a lesson id isn't found in the data store and trying to return a Json response anyway — panicking on the None case can send the client an HTTP 500 (internal server error); an explicit 404 status should be returned instead.
  • Writing a handler function as `fn` (non-async) and then trying to call a database or file operation that needs `.await` — this is a compile error; every Axum handler must be `async fn`.
  • Validate sample code in a local or test environment before applying it to a production system.

Exercise

Add a route with a query parameter, `GET /lessons?difficulty=basic` — use the `Query<HashMap<String, String>>` extractor to read the `difficulty` value and return a matching lesson list (dummy data) as a JSON array.

You'll know it worked when: A `GET /lessons/1` request returns the JSON response `{"id":1,"title":"Ownership","word_count":620}`; requesting a missing id like `GET /lessons/99` returns an HTTP 404 status instead.

Project — Axum API Endpoint | Thuta Learning