Building an API with Axum
Why Axum?
Section titled “Why Axum?”If you come from Node.js, you probably know Express or NestJS. Axum is Rust’s answer to that space — a high-level web framework that lets you write async fn route handlers, parse request data with type-safe extractors, and share state across routes.
But unlike Express, Axum is not batteries-included in the monolith sense. It is deliberately thin and delegates middleware to Tower, an industrial-strength, composable middleware library. The result is:
- Compile-time type safety across the entire request/response cycle.
- Zero-cost abstractions — no garbage collector, no hidden allocations.
- First-class async built on Tokio, the de facto Rust async runtime.
Axum vs Express vs NestJS
Section titled “Axum vs Express vs NestJS”| Feature | Express | NestJS | Axum |
|---|---|---|---|
| Language | TypeScript / JS | TypeScript | Rust |
| Routing | app.get(...) | @Get(...) decorator | Router::new().route(...) |
| Middleware | app.use(...) | @Middleware / guards | Tower layers |
| Request parsing | req.body, req.params | @Body(), @Param() | Extractors (Json, Path, Query) |
| State | app.locals | Dependency injection | State(...) extractor |
| Error handling | next(err) middleware | Exception filters | Result<T, E> + IntoResponse |
| Type safety | Partial (runtime) | Partial (runtime) | Full (compile-time) |
// Expressimport express from 'express';const app = express();
app.get('/', (req, res) => { res.json({ message: 'Hello, World!' });});
app.listen(3000, () => console.log('Listening on :3000'));use axum::{routing::get, Json, Router};use serde_json::{json, Value};
async fn hello() -> Json<Value> { Json(json!({ "message": "Hello, World!" }))}
#[tokio::main]async fn main() { let app = Router::new().route("/", get(hello)); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); println!("Listening on :3000"); axum::serve(listener, app).await.unwrap();}Run locally — Axum binds a port and runs forever, so these server snippets cannot run in the browser sandbox. Use
cargo runafter following the project setup in the next lesson.
What you will build
Section titled “What you will build”Throughout this module you will build a small Users API with:
GET /users— list users (pagination withQuery)POST /users— create a user (Jsonbody)GET /users/:id— get a single user (Path)DELETE /users/:id— delete a user- Shared in-memory state (
Arc<Mutex<Vec<User>>>) - Structured JSON errors
- A Tower CORS layer
- SQLx database integration (conceptual)
- Handler tests with
tower::ServiceExt::oneshot
Each concept maps directly to something you already know from Express or NestJS.