Build a REST API in Rust with Axum

A
Author
17 Jun, 2026 1 min read 976 views

Axum is a popular Rust web framework from the Tokio team. Handlers are plain async functions, and "extractors" in their parameters pull data out of the request in a type-safe way.

Dependencies

[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }

The API

use axum::{
    extract::{Path, State},
    http::StatusCode,
    routing::get,
    Json, Router,
};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::{Arc, Mutex}};

#[derive(Clone, Serialize)]
struct Todo { id: u64, title: String, done: bool }

#[derive(Deserialize)]
struct NewTodo { title: String }

#[derive(Clone, Default)]
struct AppState { todos: Arc<Mutex<HashMap<u64, Todo>>> }

async fn list(State(state): State<AppState>) -> Json<Vec<Todo>> {
    Json(state.todos.lock().unwrap().values().cloned().collect())
}

async fn create(State(state): State<AppState>, Json(input): Json<NewTodo>) -> (StatusCode, Json<Todo>) {
    let mut todos = state.todos.lock().unwrap();
    let id = todos.len() as u64 + 1;
    let todo = Todo { id, title: input.title, done: false };
    todos.insert(id, todo.clone());
    (StatusCode::CREATED, Json(todo))
}

async fn get_one(State(state): State<AppState>, Path(id): Path<u64>) -> Result<Json<Todo>, StatusCode> {
    state.todos.lock().unwrap().get(&id).cloned().map(Json).ok_or(StatusCode::NOT_FOUND)
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/todos", get(list).post(create))
        .route("/todos/{id}", get(get_one))
        .with_state(AppState::default());

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

How it fits together

  • Path, Query, Json and State are extractors; a malformed request is rejected before your handler runs.
  • Anything that implements IntoResponse can be returned: a Json, a status code, a tuple of both, or a Result.
  • Path parameters use {id} syntax in Axum 0.8.

Next steps

  • Swap the HashMap for PostgreSQL with sqlx and a connection pool in AppState.
  • Add middleware from tower-http: tracing, CORS, compression, timeouts.
  • Define an error enum that implements IntoResponse for consistent JSON errors.
Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.