REST API with Axum
Use case
You need to build an HTTP API: JSON endpoints, GET/POST, path and query params. Axum is a popular async web framework built on Tokio and Tower. It fits well with serde for JSON.
In C#: Like ASP.NET Core Web API: controllers, routes, model binding, JSON serialization. In Rust, axum uses handlers and routers; no built-in DI—you pass state explicitly.
Basic setup
Create a router, attach handlers to paths, and run the server with axum::serve and Tokio.
use axum::{Router, routing::get};
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(|| async { "Hello" }));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}In C#: Like app.MapGet("/", () => "Hello"); and app.Run(). The listener is like UseUrls("http://0.0.0.0:3000").
JSON request and response
Use axum::Json to extract and return JSON. Derive serde::Deserialize for request bodies and Serialize for responses.
use axum::{Json, extract::Path};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[derive(Serialize)]
struct User {
id: i32,
name: String,
}
async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
Json(User {
id: 1,
name: payload.name,
})
}
async fn get_user(Path(id): Path<i32>) -> Json<User> {
Json(User { id, name: "Alice".into() })
}
// Router::new()
// .route("/users", post(create_user))
// .route("/users/{id}", get(get_user))In C#: Like [FromBody] CreateUser payload and return Ok(user). Path params are like [FromRoute] int id.
Matching JSON field names (camelCase)
Rust structs use snake_case, but JSON/JavaScript clients usually expect camelCase. Add
#[serde(rename_all = "camelCase")] to your request/response types so the wire format is
camelCase while your code stays idiomatic — otherwise a display_name field silently
becomes "display_name" in the API and breaks a displayName-expecting client:
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct User {
id: i32,
display_name: String, // <-> JSON "displayName"
}In C#: like setting JsonNamingPolicy.CamelCase. See module 25 for the full set of
serde attributes (rename, default, skip_serializing_if, tagged enums).
Extractors
Axum uses extractors: types that pull data from the request (body, path, query, headers). Common ones:
Json<T>– JSON body (requiresDeserialize).Path<T>– path params (e.g./users/{id}).Query<T>– query string (e.g.?page=1).State<S>– shared app state (clone or Arc).
In C#: Like model binding: [FromBody], [FromRoute], [FromQuery], and injected services.
Key takeaway
Use Router::new().route("/path", get(handler).post(handler)) to define routes. Use Json<T> for JSON in/out, Path<T> and Query<T> for params. Run with axum::serve(listener, app). All handlers are async; use #[tokio::main] for main.