Skip to main content

Axum-First Web Engineering

Watch First

Why This Matters

Axum lets Rust web APIs stay close to plain Rust functions while still using typed request extraction, shared state, Tower middleware, and structured responses.

The engineering goal is thin handlers and clear boundaries, not a web framework-shaped application.

What You Will Build

Build an Axum API with /health, /tasks, /artifacts, and /jobs, using typed extractors, clean response DTOs, and route-level structure.

Concept

Axum's mental model is small:

  • routers connect paths to handlers,
  • extractors parse request input,
  • handlers call application services,
  • responses turn service results into HTTP,
  • state carries shared dependencies,
  • middleware handles cross-cutting concerns.

Rust Pattern

Keep handlers thin:

use axum::{extract::State, http::StatusCode, Json};
use serde::{Deserialize, Serialize};

#[derive(Clone)]
pub struct AppState {
pub tasks: TaskService,
}

#[derive(Debug, Deserialize)]
pub struct CreateTaskRequest {
pub title: String,
}

#[derive(Debug, Serialize)]
pub struct TaskResponse {
pub id: String,
pub title: String,
}

pub async fn create_task(
State(state): State<AppState>,
Json(request): Json<CreateTaskRequest>,
) -> Result<(StatusCode, Json<TaskResponse>), ApiError> {
let task = state.tasks.create(request.try_into()?).await?;
Ok((StatusCode::CREATED, Json(task.into())))
}

The handler extracts, converts, calls a service, and maps the response. Business rules live elsewhere.

Practice

Keep this mistake out of your first implementation.

Large handlers become unreviewable:

handler = parse JSON + validate + SQL + authorization + domain rules + logging + response mapping

Split by responsibility before the handler becomes the application.

Keep these concrete mistakes out of your work.

  • Mixing SQL, validation, authorization, and response mapping in one handler.
  • Using global mutable state instead of State<AppState>.
  • Returning inconsistent error response shapes.
  • Generating route files with no visible service boundary.

Use this sequence. Do not move to the next row until you have produced the artifact in the right column.

StepFocusArtifact
Axum mental modelRouter, route, handler, extractor, response, state, TowerArchitecture note
Routing cleanlyNested routers, versioning when neededRoute modules
HandlersThin handlers and service callscreate_task handler
ExtractorsPath, Query, Json, State, headersTyped request parsing
Application statePools, config, services, clientsAppState
Responses and DTOsStatus codes, JSON bodies, pagination, error envelopesResponse types
Middleware and TowerRequest IDs, tracing, CORS, auth, timeoutsMiddleware stack
OpenAPIGenerated docs from typesAPI docs route

Build this now. Keep each change small enough that you can run cargo check, cargo test, and inspect the diff.

Implement these endpoints:

  • GET /health
  • POST /tasks
  • GET /tasks/:id
  • GET /tasks?limit=20
  • POST /artifacts

Then refactor one oversized handler into request DTO, command conversion, service call, and response DTO.

After your own attempt, use another reviewer or an AI tool as a second pass. Accept a suggestion only when you can explain why it preserves the lesson design.

Ask AI to generate an Axum CRUD route. Review whether it:

  • keeps the handler thin,
  • maps domain errors into HTTP errors consistently,
  • uses typed extractors,
  • avoids database calls in the handler,
  • includes tests for success and failure cases.

You can move on when these statements are true.

  • Is the handler mostly glue?
  • Are request and response types explicit?
  • Does the domain avoid Axum imports?
  • Are error envelopes consistent?
  • Is app state passed deliberately?
  • Are routes grouped by feature?

Curated Resources

  • Axum documentation — the core reference for routers, handlers, extractors, responses, and state.
  • Tower documentation — Axum middleware is Tower-based, so this explains the service/layer model.
  • tower-http documentation — practical middleware for tracing, CORS, compression, request IDs, and timeouts.

Next Step

Continue to Persistence and Reusable CRUD With SQLx.