Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
82: Building a Simple Web API with Axum
I see this a lot when developers move from Node.js or Python to Rust: they assume that because Rust is a "systems language," building a web API means they'll be spending their time manually managing TCP sockets, parsing raw HTTP byte streams, or fighting a losing battle with the borrow checker in every single route handler. They expect it to feel like writing a driver for a network card.
The myth: "Web APIs in Rust are manual labor"
If you think you have to manually decode JSON strings or manage the lifecycle of an HTTP connection, you're thinking about Rust in 2015. Modern Rust web development, and Axum specifically, is built on the "extractor" pattern. You don't "pull" data out of a request; you declare what you need in your function signature, and Axum provides it to you. If the data isn't there or is malformed, Axum rejects the request before your code even runs.
Let's look at a real scenario. Imagine we're building a small API for a Book Inventory system. We aren't just returning "Hello World"—we need to handle state, paths, and JSON payloads.
use axum::{
routing::{get, post},
extract::{Path, State},
Json, Router,
};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Clone)]
struct Book {
title: String,
author: String,
}
// Our shared application state
type SharedState = Arc<Mutex<HashMap<u32, Book>>>;
#[tokio::main]
async fn main() {
let shared_state = Arc::new(Mutex::new(HashMap::new()));
let app = Router::new()
.route("/books/:id", get(get_book))
.route("/books", post(add_book))
.with_state(shared_state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
The reality: Declarative handlers via Extractors
Look at how we'd implement the handlers for the code above. Notice that I'm not calling request.get_param("id") or json.parse(body). I'm just naming the types I want in the arguments.
async fn get_book(
Path(id): Path<u32>,
State(state): State<SharedState>
) -> Result<Json<Book>, axum::http::StatusCode> {
let books = state.lock().unwrap();
books.get(&id)
.cloned()
.map(Json)
.ok_or(axum::http::StatusCode::NOT_FOUND)
}
async fn add_book(
State(state): State<SharedState>,
Json(payload): Json<Book>
) -> axum::http::StatusCode {
let mut books = state.lock().unwrap();
let id = books.len() as u32 + 1;
books.insert(id, payload);
axum::http::StatusCode::CREATED
}
The Path<u32> and Json<Book> parts are the extractors. Axum looks at the request, sees it's a GET request with a variable in the URL, and attempts to parse that variable into a u32. If the user sends /books/abc, Axum will automatically return a 400 Bad Request. Your handler logic stays clean because the "plumbing" is handled by the type system.
Handling the "State Struggle" with Arc and Mutex
Here is where most people actually trip up: sharing data across threads. Because Axum handles requests concurrently using tokio, your state must be Send and Sync. You can't just have a global HashMap; the compiler will scream at you.
I used Arc<Mutex<...>> here. The Arc (Atomic Reference Counted) pointer lets multiple threads own a reference to the data, and the Mutex ensures only one thread can modify the map at a time. In a production app, you'd likely use a database pool (like sqlx), but for in-memory stores, this pattern is the gold standard. Just be careful: keep your lock guards short. Don't hold a MutexGuard across an .await point, or you'll risk deadlocking your entire server.
📋 Practical Task
Build a Vintage Synth Inventory Manager
Your task is to expand the concepts from this lesson to create a specialized API for a vintage synthesizer shop. You need to implement a system that tracks synths, their brand, and their price.
Requirements:
- Data Model: Create a
Synthstruct with fields forname(String),brand(String), andprice(f64). - The Store: Use a
HashMap<u32, Synth>wrapped inArc<Mutex>as your application state. - Endpoint 1:
POST /synths- Accepts a JSON body to add a new synth to the inventory. Return201 Created. - Endpoint 2:
GET /synths/:id- Returns the details of a specific synth. Return404 Not Foundif the ID doesn't exist. - Endpoint 3:
GET /synths- Return a list (Vec) of all synths currently in the inventory as JSON.
Constraints: Ensure you use Axum's State and Json extractors. Your API should be able to handle multiple concurrent requests without crashing or failing borrow-checker checks.
There are no comments for now.