# axum From Zero

> Learn the Tokio team's Rust web framework - the one this very platform runs on: the router and your first server, routing and extractors, handlers and IntoResponse, shared state, middleware with Tower, a full REST API, error handling that uses Rust's type system, and testing and production. Built on hyper and tower, ergonomic without macros.


---

# axum From Zero

axum is the web framework from the Tokio team, and it has quietly become the default for new Rust web
services - including the one serving this very page. Its appeal is that it leans entirely on Rust's type
system instead of macros or magic: you write plain `async fn` handlers, and axum figures out how to call
them based on their argument and return types. It sits on top of **hyper** (the HTTP implementation) and
**tower** (a universal middleware abstraction), so learning axum also teaches you the layer the rest of
the async Rust ecosystem shares.

The mental model is two ideas. A **`Router`** maps paths to handlers (and can be nested and layered). And
a handler is **an `async fn` whose arguments are *extractors*** - types like `Path`, `Query`, `Json`, and
`State` that pull pieces out of the request - **and whose return value implements `IntoResponse`**. Once
you see "arguments extract from the request, the return value becomes the response," axum stops looking
clever and starts looking inevitable.

> 📝 This teaches the **framework** - it assumes you know **Rust**: ownership, traits, `Result`, and
> `async`/`await` ([Rust From Zero](/guides/rust-from-zero)). It builds on **Tokio** and **hyper/tower**,
> which have their own roots guides ([Tokio](/guides/tokio-the-async-runtime),
> [hyper & tower](/guides/hyper-and-tower)) - read those to remove the last of the magic. Compare with
> [actix-web](/guides/actix-web-from-zero) and [Rocket](/guides/rocket-from-zero). axum compiles and runs
> as a Rust program, so examples are shown with the commands to run them.

## How to read this

Read in order - it grows one service (a small **books API**) from a single route to a tested, deployable
REST API. Phases carry difficulty badges.

## The phases

**Part 1 - The core (🟢 Basic → 🟡)**
1. **[What axum Is & Your First Server](01-what-axum-is.md)** 🟢 - the `Router`, the async handler, and a running server on Tokio.
2. **[Routing & Extractors](02-routing-and-extractors.md)** 🟢 - methods, `Path`/`Query`, nesting and merging routers.
3. **[Handlers & IntoResponse](03-handlers-and-intoresponse.md)** 🟡 - `Json` in and out, what makes a valid handler, and how return types become responses.

**Part 2 - A real API (🟡 → 🔴)**
4. **[Shared State](04-shared-state.md)** 🟡 - `State<T>`, `with_state`, and giving handlers a database pool without globals.
5. **[Middleware with Tower](05-middleware-and-tower.md)** 🔴 - `Layer`/`Service`, `ServiceBuilder`, and `tower-http` (tracing, CORS, timeouts).
6. **[Building a REST API](06-building-a-rest-api.md)** 🟡 - full CRUD wired through extractors, state, and `IntoResponse`.
7. **[Error Handling](07-error-handling.md)** 🔴 - a custom error type that implements `IntoResponse`, and the `?` operator in handlers.

**Part 3 - Ship it (🟡 → 🟢)**
8. **[Testing & Production](08-testing-and-production.md)** 🟡 - `oneshot` handler tests, graceful shutdown, and deployment.
9. **[Where to Go Next](09-where-to-go-next.md)** 🟢 - axum vs actix/Rocket, sqlx/SeaORM for data, and the tokio/hyper/tower roots.

> The throughline: a **`Router`** sends a request to an **`async fn` whose arguments extract from it and
> whose return value becomes the response**, with tower layers wrapped around the whole thing. Hold that
> and axum is plain Rust.


---

# What axum Is & Your First Server

You know [Rust](/guides/rust-from-zero) and want to put something on the web. The Rust web ecosystem
looks intimidating from outside - async, runtimes, crates named hyper and tower. axum's pitch: make that
disappear and leave you writing ordinary Rust.

Here's what makes axum different from frameworks in other languages: it leans on **Rust's type system**
instead of macros or magic strings. A handler is a plain `async fn` - no `#[route("/users")]` annotation,
no decorator, no registration macro. You write a normal function, and axum figures out how to call it from
its argument and return types. See that, and the rest of the framework stops looking clever and starts
looking obvious.

💡 This platform - The Missing Manual - runs on axum. It comes from the **Tokio** team and sits on two
layers you'll meet later: **hyper** (the HTTP implementation) and **tower** (the shared middleware
abstraction). You don't need either to start - axum is a thin, ergonomic skin over crates the whole
async-Rust world shares, not a walled garden. (New to what a framework buys you over raw sockets? See
[What a Framework Even Is](/guides/what-a-framework-even-is).)

## The mental model: a Router maps paths to handlers

Before any code, hold one picture in your head - it's the whole framework.

📝 A **`Router`** maps **paths** to **handlers**. You build it once at startup, registering routes on it,
and hand it to the server to run.

📝 A **handler** is an **`async fn` whose return value becomes the response**. Return a `&'static str` and
axum sends it as text. Return a `String`, same thing. Return JSON, and it serializes it and sets the
header for you. The return type *is* the response - axum knows how to turn it into one because it
implements a trait called **`IntoResponse`** (more on that in Phase 3).

A handler's *arguments* are the other half: **extractors** that pull pieces out of the request (path,
query string, JSON body) - the star of the next two phases. Your first server's handler takes none.

```mermaid
flowchart LR
  R[Router<br/>maps paths to handlers] --> M["route<br/>GET /"]
  M --> H["handler<br/>async fn root()"]
  H --> RESP["return value<br/>becomes the response"]
```

*One idea:* a request comes in, the Router matches it to a route, calls that route's handler, and
whatever the handler returns is sent back. Every endpoint you build flows along that arrow.

## Your first server

First, add the two dependencies. From inside your Cargo project:

```bash
cargo add axum
cargo add tokio --features full
```

*What just happened:* `cargo add axum` pulls in the framework. `cargo add tokio --features full` adds the
async runtime axum runs on, with everything enabled (TCP listener, multi-threaded scheduler, macros). Both
now sit in `Cargo.toml`.

Now the smallest server that does something real. Put this in `src/main.rs`:

```rust
use axum::{routing::get, Router};

async fn root() -> &'static str {
    "Hello from axum"
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(root));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
```

*What just happened:* walk it from the top - 
- `async fn root() -> &'static str` is the **handler**. It takes no arguments and returns a string slice.
  Because `&'static str` implements `IntoResponse`, axum knows how to send it back as a `200 OK` with that
  text as the body. No annotation on the function - it's a plain `async fn`.
- `#[tokio::main]` is the one macro you'll use. It rewrites your `async fn main` so it can run on the Tokio
  runtime - Rust's `main` can't normally be `async`, and this bridges that gap (more on *why* below).
- `Router::new().route("/", get(root))` builds the **Router** and registers one route: a `GET` request to
  `/` runs the `root` handler. `get` comes from `axum::routing` - there's a `post`, `put`, `delete`, and
  so on for the other methods. We name the finished router `app`.
- `TcpListener::bind("0.0.0.0:3000")` opens a socket on port 3000. The `.await` waits for the bind to
  finish (it's an async operation), and `.unwrap()` says "if binding fails, crash" - fine for now; Phase 7
  handles errors properly.
- `axum::serve(listener, app).await` is the engine starting. It takes the listener and your router and
  runs the accept loop forever, handing each incoming request to the router. It blocks here until you stop
  the program.

Run it like any Rust binary:

```bash
cargo run
```

axum prints nothing by default and just waits for requests. Leave it running, and in another terminal hit
the route:

```bash
curl localhost:3000
```

```console
$ curl localhost:3000
Hello from axum
```

*What just happened:* `curl` sent a `GET /`. The Router matched it to your `root` handler, called it, and
the handler's return value - `"Hello from axum"` - came back as the response body. A working HTTP server
in a dozen lines, and not one of them is a macro-decorated route.

## Why async, and why a runtime?

Why is `root` `async fn`, and why does it need Tokio at all? Short version:

📝 A web server spends most of its life *waiting* - for a request, a database answer, another service.
**Async** lets one thread juggle thousands of those waits instead of blocking a whole OS thread per one.
That's how a small server handles many connections concurrently.

But Rust's `async`/`await` is just *syntax* - it describes work that can pause and resume, nothing more.
Something has to actually *drive* that work: poll paused tasks, wake them when ready, spread them across
threads. That's a **runtime**, and in axum's world it's **Tokio** - which is why `#[tokio::main]` wraps
your `main` and starts it. You don't need to understand Tokio's internals to use axum, but
[Tokio: The Async Runtime](/guides/tokio-the-async-runtime) removes the rest of the mystery.

## The running example: a books API

We won't keep returning `"Hello from axum"` - across this guide we'll grow one real service: a small
**books API**, built around one type:

```rust
struct Book {
    id: u32,
    title: String,
    author: String,
}
```

*What just happened:* we declared the `Book` struct the rest of the guide builds on - for now, a plain
struct. In Phase 3 we'll derive `Serialize`/`Deserialize` on it so axum can turn it into JSON going out and
parse it from a request body coming in - that's how a struct becomes a real API resource. You've now met
the cast: **`Router`**, **handler**, **return value** as response, and **`Book`**, which we'll spend the
next eight phases turning into a proper REST API.

Next: routing - methods, path/query parameters (your first extractors), and nesting routers so a growing
API doesn't sprawl into one giant list.

## Recap

- **axum leans on Rust's type system, not macros.** A handler is a plain `async fn` - no route
  annotations, no decorators. axum calls it based on its argument and return types.
- **The mental model is one sentence:** a **`Router`** maps paths to handlers, and a handler is an
  **`async fn` whose return value becomes the response** (because that value implements `IntoResponse`).
  Its arguments - the extractors - come in Phases 2–3.
- **A first server is small:** `cargo add axum` and `cargo add tokio --features full`, build a
  `Router::new().route("/", get(root))`, bind a `TcpListener`, and call `axum::serve(listener, app)`. Run
  with `cargo run`, test with `curl`.
- **`#[tokio::main]` starts the runtime** so your `async main` can run. axum is async because servers
  spend their lives waiting, and **Tokio** is the runtime that drives that async work.
- **axum sits on Tokio plus hyper/tower** - it's a thin, ergonomic layer, not a walled garden. This very
  platform runs on it.
- **The throughline:** Router → handler → return value → response. We'll grow one **books API** along
  that arrow for the rest of the guide.

## Quick check

Three questions on the ideas that have to stick - what makes axum different, the Router/handler model,
and how a first server fits together:

```quiz
[
  {
    "q": "What makes an axum handler different from route definitions in many other frameworks?",
    "choices": [
      "It's a plain async fn with no route annotation - axum calls it based on its argument and return types",
      "It must be decorated with a #[route] macro that declares its path and method",
      "It has to be registered in a global config file before it can be called",
      "It must return a special Response object built by hand every time"
    ],
    "answer": 0,
    "explain": "axum leans on Rust's type system instead of macros. A handler is an ordinary async fn; you wire it to a path with Router::new().route(...), and axum figures out how to call it and how to turn its return value into a response."
  },
  {
    "q": "In `Router::new().route(\"/\", get(root))`, what does the return value of the `root` handler become?",
    "choices": [
      "The HTTP response sent back to the client, because the return type implements IntoResponse",
      "A log line printed to the server console",
      "An argument passed into the next handler in the chain",
      "Nothing - you must call a separate function to send the response"
    ],
    "answer": 0,
    "explain": "A handler's return value becomes the response. Types like &'static str, String, and Json<T> implement IntoResponse, so axum knows how to turn them into a full HTTP response automatically."
  },
  {
    "q": "Why does the first server use `#[tokio::main]` and depend on Tokio?",
    "choices": [
      "Tokio is the async runtime that drives axum's async work; #[tokio::main] starts it so main can be async",
      "Tokio is a database that axum requires to store routes",
      "Tokio compiles the handlers to machine code at startup",
      "Tokio is only needed in production, never during local development"
    ],
    "answer": 0,
    "explain": "Rust's async/await is just syntax - something has to poll and wake paused tasks. That's the runtime, Tokio. #[tokio::main] starts the runtime and lets your main function be async, which axum::serve needs."
  }
]
```


---

# Routing & Extractors

Phase 1 gave you a single route answering a single path. Real APIs branch: `GET /books` lists, `POST
/books` creates, `GET /books/42` shows one. This phase covers how axum picks *which* handler runs, and how
a handler reaches into the request and pulls out exactly the data it wants.

📝 **The mental model, and it's the whole framework:** a route is **method + path → handler**. A handler's
**parameters are extractors** - each one a type that knows how to pull a specific piece out of the
incoming request. `Path<u32>` pulls a URL segment, `Query<T>` pulls the query string; later you'll meet
`Json<T>` (the body) and `State<T>` (shared data). You don't parse the request yourself - you *declare what
you need by type*, and axum fills it in before your function body runs.

## Methods: one path, many verbs

A route ties an HTTP method to a handler. The method helpers live in `axum::routing`: `get`, `post`, `put`, `delete`, `patch`. You can chain them on a single path, which is exactly what you want for a REST resource.

We're growing a small **books API** this whole guide. Each book is just:

```rust
struct Book {
    id: u32,
    title: String,
    author: String,
}
```

Here's the router for it:

```rust
use axum::{
    Router,
    routing::get,
};

async fn list_books() -> &'static str {
    "all books"
}

async fn create_book() -> &'static str {
    "created a book"
}

async fn show_book() -> &'static str {
    "one book"
}

fn app() -> Router {
    Router::new()
        .route("/books", get(list_books).post(create_book))
        .route("/books/{id}", get(show_book))
}
```

*What just happened:* the first `route` maps **two** verbs to the same path - `get(list_books).post(create_book)`. A `GET /books` runs `list_books`; a `POST /books` runs `create_book`; anything else on that path gets an automatic `405 Method Not Allowed`. The second route introduces a **path parameter**: `{id}` is a capture, a placeholder matching any single segment, so `/books/42` and `/books/abc` both match `/books/{id}` - but the handler hasn't read that segment yet. That's the extractor's job, next.

💡 **A version note that will save you a confusing afternoon:** the `{id}` curly-brace syntax is **axum 0.8**. If you're reading older blog posts or a 0.7 codebase, captures looked like `:id` (`"/books/:id"`). Same idea, different punctuation. This guide uses `{id}` throughout; if your compiler complains about the braces, check your axum version in `Cargo.toml`.

## The `Path` extractor: reading the URL

A capture in the route is only half the deal. To actually *use* `42`, you add a `Path` parameter to the handler:

```rust
use axum::extract::Path;

async fn show_book(Path(id): Path<u32>) -> String {
    format!("showing book {id}")
}
```

*What just happened:* `Path<u32>` is the extractor. axum looks at the matched route, finds the `{id}` segment, tries to parse it as a `u32`, and hands it to your function. The `Path(id)` part is just Rust pattern-matching - `Path` is a tuple struct wrapping one value, so `Path(id)` destructures it and binds the inner `u32` to `id`. (If you find that line noisy, you could write `path: Path<u32>` and use `path.0` instead - same thing, less idiomatic.)

The parse is real and it matters: a request to `/books/42` gives you `id = 42`. A request to `/books/abc` can't parse as `u32`, so axum rejects it with `400 Bad Request` *before your handler runs*. You never see the bad input - the type *is* the validation.

When a path has **multiple** captures, `Path` extracts a tuple, in order:

```rust
use axum::extract::Path;

// route: .route("/authors/{author}/books/{id}", get(show_authored_book))
async fn show_authored_book(Path((author, id)): Path<(String, u32)>) -> String {
    format!("book {id} by {author}")
}
```

*What just happened:* Two captures (`{author}`, `{id}`) map to a two-element tuple `(String, u32)`, **positionally** - first segment to the first type, second to the second. So `/authors/tolkien/books/7` gives `author = "tolkien"`, `id = 7`. Note `author` is a `String` (any text is valid) while `id` is still a `u32` (must parse as a number, or it's a `400`). Order is everything here; the names in the URL don't matter to a tuple, only the position does.

## The `Query` extractor: reading the query string

URLs carry data after the `?` too - `/books?page=2&limit=20`. That's the **query string**, and `Query<T>` extracts it into a struct of your own design. This one needs **serde**, the Rust serialization library, because axum deserializes the raw `page=2&limit=20` text into your typed struct.

Add serde with the `derive` feature:

```bash
cargo add serde --features derive
```

Then define a struct describing the parameters you expect, and extract it:

```rust
use axum::extract::Query;
use serde::Deserialize;

#[derive(Deserialize)]
struct Pagination {
    page: Option<u32>,
    limit: Option<u32>,
}

async fn list_books(Query(params): Query<Pagination>) -> String {
    let page = params.page.unwrap_or(1);
    let limit = params.limit.unwrap_or(20);
    format!("books page {page}, {limit} per page")
}
```

*What just happened:* `#[derive(Deserialize)]` teaches `Pagination` how to be built from the query string. `Query<Pagination>` then parses `?page=2&limit=20` into `Pagination { page: Some(2), limit: Some(20) }`. The two fields being `Option<u32>` makes both parameters **optional** - a bare `GET /books` still succeeds, both fields come back `None`, and `unwrap_or` supplies defaults. Type them as plain `u32` instead and a request missing `page` gets rejected with a `400`: `Option` vs. required encodes "optional vs. mandatory" directly in the type.

💡 The same pattern handles filters and flags: a field `author: Option<String>` lets `/books?author=tolkien` flow straight into a typed field. The struct *is* your query API.

## Nesting and merging: structure for a growing API

One flat `Router` works until you have a dozen routes and want to version them, or split them across files. Two methods compose routers:

- **`nest("/prefix", other)`** mounts a sub-router *under a path prefix*. Everything inside `other` gains that prefix. This is your versioning tool.
- **`merge(other)`** folds another router's routes into this one *at the same level*, no prefix. Good for splitting routes across modules without changing their paths.

Here's the books API mounted under `/api/v1`:

```rust
use axum::{Router, routing::get};

fn books_router() -> Router {
    Router::new()
        .route("/books", get(list_books).post(create_book))
        .route("/books/{id}", get(show_book))
}

fn app() -> Router {
    Router::new()
        .nest("/api/v1", books_router())
}
```

*What just happened:* `books_router()` defines paths as if they lived at the root - `/books`, `/books/{id}`. `nest("/api/v1", ...)` prefixes them all, so the reachable URLs become `/api/v1/books` and `/api/v1/books/{id}`. When v2 arrives, write a `books_router_v2()` and `.nest("/api/v2", ...)` it alongside - v1 keeps working untouched, because the prefix lives in *one* place, not sprinkled across every route string. Reach for `merge` instead when combining routers that should share the same prefix level, like a `users_router()` and `books_router()` both under `/api/v1`.

⚠️ **A rule that'll bite you in Phase 3, so plant it now:** an extractor that **consumes the request body** - like `Json<T>`, which you'll meet next phase for reading POST payloads - can appear **only once per handler, and it must be the last parameter.** The body is a stream you can read exactly once, so axum enforces this at compile time. `Path` and `Query` don't touch the body, so they can come in any order and any number. The moment you add a body extractor, it goes at the end: `async fn create_book(Path(id): Path<u32>, Json(body): Json<NewBook>)`. Get the order wrong and you'll get a trait-bound error that looks scary but means exactly this. (Full story in Phase 3.)

## Recap

- A route is **method + path → handler**; chain verbs on one path with `get(h).post(h2)`, and unmatched methods auto-return `405`.
- Path captures use **`{id}`** in axum 0.8 (older `:id` in 0.7); a capture in the route only matches a segment - an **extractor** reads it.
- **`Path<T>`** pulls URL segments by type (a tuple `Path<(A, B)>` for multiple, positionally); a parse failure is an automatic `400`.
- **`Query<T>`** deserializes the query string into a `#[derive(Deserialize)]` struct (needs serde); `Option<_>` fields make parameters optional.
- **`nest("/prefix", r)`** mounts a sub-router under a prefix (use it for versioning like `/api/v1`); **`merge(r)`** combines routers at the same level.
- Body-consuming extractors (e.g. `Json`) must be the **last** parameter and appear **once** - non-body extractors like `Path`/`Query` have no such limit.

## Quick check

```quiz
[
  {
    "q": "In axum 0.8, how do you declare a path that captures a book id?",
    "choices": ["\"/books/:id\"", "\"/books/{id}\"", "\"/books/<id>\"", "\"/books/[id]\""],
    "answer": 1,
    "explain": "axum 0.8 uses curly braces: \"/books/{id}\". The colon form \":id\" was the 0.7 syntax."
  },
  {
    "q": "A handler takes Query(params): Query<Pagination> where page is Option<u32>. What happens on a request to /books with no query string?",
    "choices": ["It returns 400 Bad Request", "It succeeds and page is None", "It panics", "It returns 404 Not Found"],
    "answer": 1,
    "explain": "Because page is Option<u32>, the parameter is optional. Missing it yields None and the handler runs normally. A plain u32 field would have forced a 400."
  },
  {
    "q": "Which statement about extractor order in a handler is true?",
    "choices": ["Path must always come first", "A body extractor like Json must be the last parameter and appear only once", "Query must be the last parameter", "Order never matters for any extractor"],
    "answer": 1,
    "explain": "The request body can be read only once, so a body-consuming extractor (Json) must be last and singular. Non-body extractors like Path and Query can appear in any order."
  }
]
```


---

# Handlers & IntoResponse

Phase 2 pulled pieces out of the URL with `Path` and `Query`. Now we close the loop. A handler isn't a
special kind of function with a magic signature to memorize - it's an ordinary `async fn` that obeys one
rule on each side.

📝 **The mental model:** *the arguments extract FROM the request; the return value becomes the response.*
Every parameter is a type that knows how to read part of the incoming request; the return type knows how
to turn itself into an outgoing response. Once that clicks, you stop guessing at signatures and start
*deriving* them: "I need the JSON body, so I take `Json<T>`; I want to send a created book with a 201, so I
return `(StatusCode, Json<Book>)`." The framework wires the rest.

We'll keep growing the **books API**. The types from earlier:

```rust
use serde::{Deserialize, Serialize};

#[derive(Serialize)]
struct Book {
    id: u64,
    title: String,
    author: String,
}

#[derive(Deserialize)]
struct NewBook {
    title: String,
    author: String,
}
```

*What just happened:* `Book` derives `Serialize` because it travels *out* (we turn it into JSON for the
response). `NewBook` derives `Deserialize` because it comes *in* (we build it from the request body). The
direction of travel decides the derive - that distinction will matter in every handler below.

## Json as an input

To accept a JSON request body, take `Json<T>` as a parameter, where `T` derives `Deserialize`. axum reads
the body, parses it as JSON, and hands you the deserialized value.

```rust
use axum::Json;

async fn create_book(Json(payload): Json<NewBook>) -> String {
    format!("Got a new book: {} by {}", payload.title, payload.author)
}
```

*What just happened:* `Json(payload): Json<NewBook>` destructures the extractor right in the parameter
list, so inside the function `payload` is a plain `NewBook` - no unwrapping. If the body is missing or
isn't valid JSON for `NewBook`, axum rejects the request with a `400 Bad Request` before your code ever
runs. You write the happy path; the extractor guards the door.

⚠️ **`Json<T>` as an extractor must be the *last* parameter.** Reading the body consumes the request, so
it has to come after extractors like `Path` and `Query` that only peek at the headers and URL. This is the
right order:

```rust
use axum::extract::Path;
use axum::Json;

async fn replace_book(
    Path(id): Path<u64>,
    Json(payload): Json<NewBook>,
) -> String {
    format!("Replacing book {id} with {} by {}", payload.title, payload.author)
}
```

*What just happened:* `Path(id)` comes first (it reads from the URL), `Json(payload)` comes last (it
consumes the body). Put `Json` before `Path` and you get a compile error, because axum only lets the final
argument be a body-consuming extractor. When in doubt: body last.

## Json as an output, and IntoResponse

The same `Json` type works in reverse. Return `Json(value)` where `value`'s type derives `Serialize`, and
axum serializes it and sets `Content-Type: application/json` for you.

```rust
use axum::Json;

async fn get_book() -> Json<Book> {
    let book = Book {
        id: 1,
        title: "The Rust Programming Language".into(),
        author: "Klabnik & Nichols".into(),
    };
    Json(book)
}
```

*What just happened:* the return type is `Json<Book>`. axum sees that, serializes `book` to a JSON body,
and adds the JSON content-type header. You never touched the response object directly - you returned a
*value that knows how to become a response*.

That "knows how to become a response" is a real trait: **`IntoResponse`**. A handler's return type has to
implement it, and many common types already do, so you rarely write one yourself:

- `&str` and `String` - a `200 OK` with a plain-text body.
- `Json<T>` - a JSON body (when `T: Serialize`).
- `StatusCode` - an empty response with just that status (e.g. `StatusCode::NO_CONTENT`).
- `(StatusCode, T)` - set the status *and* a body, where `T` is itself `IntoResponse`.
- `(StatusCode, HeaderMap, T)` - status, custom headers, and a body.
- `Html<_>` - an HTML body with the right content-type.
- `Result<T, E>` - succeed with `T` or fail with `E`, when **both** implement `IntoResponse`.

The tuple forms are the workhorses. Here's the canonical "create" handler that returns a `201 Created`
along with the new book as JSON:

```rust
use axum::http::StatusCode;
use axum::Json;

async fn create_book(Json(payload): Json<NewBook>) -> (StatusCode, Json<Book>) {
    let book = Book {
        id: 42,
        title: payload.title,
        author: payload.author,
    };
    (StatusCode::CREATED, Json(book))
}
```

*What just happened:* the return type `(StatusCode, Json<Book>)` is a tuple, and axum implements
`IntoResponse` for it: the first element becomes the status line (`201 Created`), the second becomes the
body (JSON). `StatusCode` lives in `axum::http::StatusCode`. This single pattern - `Json` in, status +
`Json` out - covers most write endpoints you'll ever build.

## When a handler can fail: returning a Result

Real handlers fail. A lookup misses, the input is valid JSON but semantically wrong. Because `Result<T, E>`
implements `IntoResponse` (as long as both `T` and `E` do), you can return one straight from a handler:

```rust
use axum::extract::Path;
use axum::http::StatusCode;
use axum::Json;

async fn find_book(Path(id): Path<u64>) -> Result<Json<Book>, StatusCode> {
    if id == 1 {
        Ok(Json(Book {
            id: 1,
            title: "The Rust Programming Language".into(),
            author: "Klabnik & Nichols".into(),
        }))
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

*What just happened:* the success arm returns `Ok(Json(book))` → a `200` with a JSON body; the failure arm
returns `Err(StatusCode::NOT_FOUND)` → a bare `404`. axum unwraps the `Result` and turns whichever side
you returned into the response. This is the seed of real error handling - in **Phase 7** you'll replace
`StatusCode` with your own error *type* that implements `IntoResponse`, so `?` inside a handler maps your
domain errors to clean HTTP responses. For now, the takeaway is just: a fallible handler returns a
`Result`, and both arms have to be response-able.

## What actually makes something a handler

📝 Notice you never *registered* your functions as handlers or implemented any interface. That's because
axum implements its `Handler` trait *automatically* for any `async fn` whose arguments all implement the
extractor traits (`FromRequest` / `FromRequestParts`) and whose return type implements `IntoResponse`.
Satisfy those two conditions and the function *is* a handler - the type system, not a macro, decides what
`.route()` will accept.

⚠️ **The error you'll eventually hit.** When an argument or the return type *doesn't* satisfy those traits,
the compiler doesn't point at your function. It points at the `.route(...)` call and emits a long, scary
message like:

```text
the trait bound `fn(...) -> ...: Handler<_, _>` is not satisfied
   the following other types implement trait `Handler<T, S>` ...
   required by a bound introduced by this call
```

The first time you see it, it looks like axum is broken. It isn't. It's saying: *"this function doesn't
qualify as a handler."* Resist the urge to debug the router - the real fix is almost always in the function
signature. Run down this checklist:

1. **Is every argument an extractor?** A stray `&str` or a custom struct that isn't an extractor will break
   it.
2. **Does the return type implement `IntoResponse`?** Returning, say, a bare `Book` (when it isn't an
   extractor/response) won't compile - wrap it in `Json`.
3. **Is the body-consuming extractor (`Json<T>`, `String`, `Bytes`) the *last* argument?**
4. **Is the function `async`?**

Nine times out of ten, fixing the arguments or the return type makes the `.route()` error vanish. Read the
signature, not the router.

## Recap

- A handler is just an `async fn`: **arguments extract from the request, the return value becomes the
  response.** Memorize that, not signatures.
- `Json<T>` is bidirectional - an extractor for the request body (`T: Deserialize`, must be the **last**
  parameter) and a response for the body (`T: Serialize`).
- The return type must implement **`IntoResponse`**. `&str`/`String`, `Json<T>`, `StatusCode`, tuples like
  `(StatusCode, Json<T>)`, `Html<_>`, and `Result<T, E>` all do.
- The everyday create pattern is `(StatusCode::CREATED, Json(book))`; `StatusCode` lives in
  `axum::http::StatusCode`.
- A fallible handler returns `Result<T, E>` where both sides are `IntoResponse` - the on-ramp to Phase 7's
  custom error type.
- A "trait bound ... `Handler` is not satisfied" error on `.route(...)` means the *function signature* is
  wrong (a non-extractor arg, a non-response return, or `Json` not last) - fix the handler, not the router.

## Quick check

```quiz
[
  {
    "q": "Why must a Json<T> extractor be the last parameter in a handler?",
    "choices": ["Rust requires generic parameters to come last", "It consumes the request body, so it must come after extractors that only read the URL and headers", "axum reads parameters right-to-left", "Json is alphabetically last among extractors"],
    "answer": 1,
    "explain": "Reading the body consumes the request, so body-consuming extractors must come after non-consuming ones like Path and Query."
  },
  {
    "q": "Which return type gives a 201 with the new book serialized as JSON?",
    "choices": ["Book", "(StatusCode, Json<Book>) returning (StatusCode::CREATED, Json(book))", "Json<StatusCode>", "String"],
    "answer": 1,
    "explain": "axum implements IntoResponse for (StatusCode, T) tuples: the StatusCode sets the status and the Json<Book> becomes the body."
  },
  {
    "q": "You get \"the trait bound ...: Handler<_, _> is not satisfied\" on a .route() call. Where is the real problem?",
    "choices": ["The router configuration", "The handler's argument or return types don't satisfy the extractor / IntoResponse traits", "A missing dependency in Cargo.toml", "The Tokio runtime isn't started"],
    "answer": 1,
    "explain": "That error means the function doesn't qualify as a handler. Check the signature: every arg an extractor, the return IntoResponse, and the body extractor last."
  }
]
```


---

# Shared State

So far every handler you've written has been a closed little world: it takes a
request apart with extractors, builds a response, and forgets everything. That's
fine for an echo endpoint, but a real books API needs to *remember things* - the
books themselves, a database connection, maybe a config loaded at boot. Where
does that live?

The instinct from other languages is a global - a static variable, a singleton,
a module-level dict the handlers all reach into. In Rust that fights you hard:
globals have to be `'static`, thread-safe, and usually `unsafe` or wrapped in a
macro to even compile. axum's answer is cleaner, and it's the whole point of
this phase.

> 📝 **Mental model:** handlers stay **stateless**. Anything shared lives in a
> **state value** that you hand to the router with **`.with_state(state)`**.
> Each handler that needs it asks for it back through the **`State` extractor** - 
> the same "argument extracts from the request" idea you already know, except
> this argument extracts from the *application*, not the HTTP request. No
> globals, no `unsafe`, fully type-checked.

## Defining and attaching state

State is just a type you define. Wire it up in three moves: declare the type,
attach an instance with `.with_state(...)`, and pull it into handlers with the
`State` extractor.

```rust
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use axum::{Json, Router, extract::State, routing::get};

#[derive(Clone)]
struct AppState {
    books: Arc<Mutex<HashMap<u32, Book>>>,
}

async fn list(State(state): State<AppState>) -> Json<Vec<Book>> {
    let books = state.books.lock().unwrap();
    Json(books.values().cloned().collect())
}

let state = AppState { books: Arc::new(Mutex::new(HashMap::new())) };
let app = Router::new().route("/books", get(list)).with_state(state);
```

*What just happened:* `AppState` is a plain struct holding our in-memory book
store. We built one instance, handed it to the router with `.with_state(state)`,
and the `list` handler asked for it back by taking `State(state): State<AppState>`
as an argument. Inside, `state.books.lock()` gives temporary exclusive access to
the `HashMap`, and we clone the values into a `Vec` to return as JSON. Notice the
handler never touched a global - the state came in the front door like any other
extractor.

The `State(state)` in the argument list is a destructuring pattern: `State` is a
wrapper tuple struct, and `State(state)` unwraps it so `state` is your bare
`AppState`. You'll see the same shape for `Path(id)` and `Json(payload)` - it's a
consistent axum idiom, not special syntax for state.

Writing works the same way. Here's the insert side of the books API:

```rust
use axum::http::StatusCode;

async fn create(
    State(state): State<AppState>,
    Json(book): Json<Book>,
) -> StatusCode {
    let mut books = state.books.lock().unwrap();
    books.insert(book.id, book);
    StatusCode::CREATED
}

let app = Router::new()
    .route("/books", get(list).post(create))
    .with_state(state);
```

*What just happened:* `create` takes **two** extractors. Order matters here:
`State` (and any other non-body extractor) comes first, and the body-consuming
`Json` comes **last** - axum only lets one extractor consume the request body,
and it has to be the final argument. We `lock()` the store, this time binding it
`mut` so we can `insert`, and return `201 Created`. Both handlers share the *same*
store because they share the same state.

## Why `Arc<Mutex<...>>` and not just a `HashMap`

Here's the insight that trips everyone up the first time. Why isn't `AppState`
this?

```rust
#[derive(Clone)]
struct AppState {
    books: HashMap<u32, Book>, // looks simpler - but it's a trap
}
```

⚠️ The state type **must be `Clone`**, because axum clones it once per request
before handing it to your handler. If `books` were a bare `HashMap`, each request
would get its own *copy* of the map. Insert a book in one request, and the next
request - working off a fresh clone of the original - wouldn't see it. You'd have
a store that silently forgets everything. It compiles; it just doesn't work.

`Arc<Mutex<HashMap<...>>>` fixes this by separating the two things `Clone` could
mean:

- **`Arc`** (atomically reference-counted pointer) makes cloning *cheap and
  shared*. Cloning an `Arc` doesn't copy the `HashMap` - it bumps a reference
  count and hands back another pointer to the **same** map. Every request's clone
  of `AppState` points at one underlying store.
- **`Mutex`** makes that shared access *safe*. Many requests run concurrently on
  different threads; without a lock, two of them writing to the same `HashMap` at
  once is a data race. `.lock()` grants one-at-a-time access and blocks the rest
  until the guard is dropped.

```rust
// Cloning AppState clones the Arc, NOT the HashMap behind it.
let a = state.clone();
let b = state.clone();
// a.books and b.books are two Arc handles to ONE shared Mutex<HashMap<...>>.
```

*What just happened:* every clone is just another pointer to the same data. That
is exactly what you want - shared, not copied. Read it for write access with
`RwLock` instead of `Mutex` (`Arc<RwLock<...>>`) when reads vastly outnumber
writes, since `RwLock` lets many readers in at once.

> 💡 You won't usually do this dance for a real database. A `sqlx::PgPool` is
> *already* `Clone` and internally an `Arc`, so you store it **directly** - no
> `Mutex` needed: `struct AppState { db: PgPool }`. The pool manages its own
> connections and concurrency. The `Arc<Mutex<...>>` pattern is for plain
> in-memory data you own, like our `HashMap` toy store.

## `State` vs `Extension`

There's an older way to share data: `Extension`. You attach a value as a layer
with `.layer(Extension(value))` and pull it out with the `Extension` extractor.

```rust
// The older Extension approach - works, but prefer State.
let app = Router::new()
    .route("/books", get(list_ext))
    .layer(Extension(state));

async fn list_ext(Extension(state): Extension<AppState>) -> Json<Vec<Book>> {
    let books = state.books.lock().unwrap();
    Json(books.values().cloned().collect())
}
```

*What just happened:* functionally this does the same job. The crucial
difference is **when mistakes are caught**. With `State`, the router won't
compile unless its state type matches what every handler asks for - wire up the
wrong type and the build fails at your desk. `Extension` is looked up by type at
**runtime**: forget to add the layer, or ask for the wrong type, and the request
panics with `500` only when that endpoint is actually hit.

> 💡 Prefer **`State`**. It's type-checked at compile time, reads cleaner, and is
> the modern axum default. Reach for `Extension` only when you genuinely can't
> know the state type at the router's construction site - for instance, when
> middleware injects per-request values further down the stack. For app-wide
> dependencies like our store or a DB pool, `State` is the right tool.

## Recap

- Handlers are **stateless**; shared dependencies live in a **state value**
  attached to the router with **`.with_state(state)`** and extracted via
  **`State<T>`**.
- The state type **must be `Clone`** - axum clones it per request. Mutable data
  must sit behind a shared pointer, or each request mutates a throwaway copy.
- **`Arc`** makes the clone *share* rather than *copy*; **`Mutex`** (or
  `RwLock`) makes that shared access thread-safe.
- A real `sqlx::PgPool` is already `Clone`/`Arc`, so you store it directly - no
  `Mutex` wrapper.
- Body-consuming extractors like `Json` go **last** in a handler's argument list;
  `State` and other non-body extractors come first.
- Prefer **`State`** (compile-time checked) over **`Extension`** (runtime
  lookup that panics if missing).

## Quick check

Lock these in before moving on to middleware.

```quiz
[
  {
    "q": "Why must your AppState type derive Clone?",
    "choices": [
      "So you can compare two states for equality",
      "Because axum clones the state once per request before handing it to the handler",
      "So Rust can store it in a global static",
      "It doesn't have to - Clone is optional on state"
    ],
    "answer": 1,
    "explain": "axum clones the state value for each request. That's why mutable data must be behind an Arc so the clone shares one store instead of copying it."
  },
  {
    "q": "What does cloning an Arc<Mutex<HashMap<...>>> actually copy?",
    "choices": [
      "The whole HashMap, deeply",
      "Nothing - it just bumps a reference count and returns another pointer to the same data",
      "Only the Mutex, but not the map inside it",
      "A snapshot of the map at clone time"
    ],
    "answer": 1,
    "explain": "Arg::clone increments a reference count and returns another handle to the same underlying Mutex<HashMap>. All clones see one shared store - exactly what shared state needs."
  },
  {
    "q": "Why prefer State over Extension for app-wide dependencies?",
    "choices": [
      "Extension is faster at runtime",
      "State is checked at compile time, while a missing or mistyped Extension panics at runtime when the endpoint is hit",
      "Extension can't hold a database pool",
      "State allows globals and Extension does not"
    ],
    "answer": 1,
    "explain": "State ties the router's state type to what handlers request, so mismatches fail to compile. Extension is resolved by type at runtime and panics with a 500 only when that route runs."
  }
]
```


---

# Middleware with Tower

Here's the mental model, and it's the whole phase: **middleware in axum is a tower `Layer` that wraps your entire service.** You don't register it into a special hook list the way you might in Express or Flask. Instead, you take your `Router` and call `.layer(...)`, which returns a *new* service with the old one tucked inside, like a Russian doll. A request travels inward through each wrapper to reach your handler, and the response travels back out through the same wrappers in reverse.

That single picture - onion layers around your handler - explains why ordering works the way it does, why a logging layer sees both the request *and* the response, and why the same layers you use here also work with HTTP clients and gRPC services.

> 📝 The deep machinery - what a `Service` trait actually is, how `Layer` composes them, why this is *poll*-based - lives in the roots guide, [hyper & tower](/guides/hyper-and-tower). You don't need it to be productive here. This phase is the *applied* view: how to reach for layers in a real axum app. When the abstraction feels like magic and you want to dispel it, that guide is where to go.

We'll keep building the **books API** from earlier phases - the one with shared state holding our books. By the end it'll log every request and reject anyone without an auth header.

## Where middleware fits

A request to your books API doesn't hit your handler directly. It passes through the layers you've wrapped around the router:

```mermaid
flowchart LR
  A[Request] --> B[Trace layer] --> C[Auth layer] --> D[Handler]
  D --> E[Response] --> C --> B --> F[Out the door]
```

Each layer can inspect or modify the request on the way in, decide whether to keep going, and inspect or modify the response on the way out. That "on the way out" half is what makes middleware powerful - a single layer wraps the *whole* request/response round trip.

## Ready-made layers from tower-http

You rarely write middleware from scratch. The **`tower-http`** crate ships the layers almost every web service needs - request logging, CORS, timeouts, compression - all as tower layers that drop straight into axum.

Add it with the features you want:

```bash
cargo add tower-http --features trace,cors,timeout
```

The most useful one to start with is **`TraceLayer`**, which logs every request and response using the `tracing` crate. Let's wrap it around the books router:

```rust
use axum::{routing::get, Router};
use tower_http::trace::TraceLayer;

let app = Router::new()
    .route("/books", get(list_books))
    .with_state(state)
    .layer(TraceLayer::new_for_http());
```

*What just happened:* `.layer(TraceLayer::new_for_http())` wrapped the entire router in a logging layer. Now every request to `/books` gets logged when it arrives and when its response is produced - latency included - without touching the `list_books` handler at all. (You'll also need a `tracing` subscriber initialized at startup for the logs to actually print, e.g. `tracing_subscriber::fmt::init();` - `TraceLayer` *emits* events; the subscriber *displays* them.)

That's the entire pattern for ready-made middleware: add the crate, construct the layer, hang it off `.layer()`. `CorsLayer` and `TimeoutLayer` work exactly the same way.

## Stacking layers with ServiceBuilder

A real service needs several layers at once. You *could* chain `.layer()` calls, but tower gives you **`ServiceBuilder`** for grouping them cleanly:

```rust
use std::time::Duration;
use tower::ServiceBuilder;
use tower_http::{timeout::TimeoutLayer, trace::TraceLayer};

let app = Router::new()
    .route("/books", get(list_books))
    .with_state(state)
    .layer(
        ServiceBuilder::new()
            .layer(TraceLayer::new_for_http())
            .layer(TimeoutLayer::new(Duration::from_secs(10))),
    );
```

*What just happened:* `ServiceBuilder::new()` starts an empty stack; each `.layer()` adds one. The whole stack gets applied to the router in one `.layer()` call. Tracing runs first (outermost), so it logs every request including ones that later time out; the timeout sits just inside it, aborting any handler that runs longer than 10 seconds.

### ⚠️ The layer-ordering rule everyone trips on

Ordering is the single most confusing thing about tower middleware, so read this twice.

When you chain bare `.layer()` calls on a `Router`, layers apply **bottom-up / outside-in** - the **last** `.layer()` you add becomes the **outermost** wrapper, meaning it runs **first** on the way in:

```rust
// inner.layer(A).layer(B)  →  B wraps A wraps handler
// Request order:  B → A → handler
let app = router
    .layer(layer_a)   // inner
    .layer(layer_b);  // OUTER - runs first
```

`ServiceBuilder` flips this to read the intuitive way: layers run **top-to-bottom as written**. The first `.layer()` in the builder is the outermost:

```rust
// Request order:  first → second → handler  (reads top-down ✓)
ServiceBuilder::new()
    .layer(first)    // OUTER - runs first
    .layer(second);  // inner
```

*What just happened:* both snippets compose layers, but they read in **opposite directions**. This is exactly why `ServiceBuilder` exists - for more than one or two layers, prefer it so the source order matches the execution order. When you see a bare `.layer().layer()` chain, remember to read it from the bottom up.

## Writing custom middleware with from_fn

When no off-the-shelf layer does what you need, the easy path is **`axum::middleware::from_fn`**. It turns a plain `async fn` into a layer. The function receives the incoming `Request` and a `Next` (the rest of the chain), and either short-circuits with a response or calls `next.run(req).await` to continue.

Here's an auth gate for the books API that rejects requests with no `authorization` header:

```rust
use axum::{
    extract::Request,
    http::StatusCode,
    middleware::{self, Next},
    response::Response,
};

async fn require_auth(req: Request, next: Next) -> Result<Response, StatusCode> {
    if req.headers().get("authorization").is_none() {
        return Err(StatusCode::UNAUTHORIZED);
    }
    Ok(next.run(req).await)
}

let app = Router::new()
    .route("/books", get(list_books))
    .with_state(state)
    .layer(middleware::from_fn(require_auth))
    .layer(TraceLayer::new_for_http());
```

*What just happened:* `from_fn(require_auth)` wraps `require_auth` into a layer. On each request, if the `authorization` header is missing, the function returns `Err(StatusCode::UNAUTHORIZED)` and the handler **never runs** - the `401` goes straight back out. Otherwise `next.run(req).await` hands control to the inner service (eventually `list_books`) and passes its response back. Note the ordering: `TraceLayer` is added *last*, so it's outermost and logs even the rejected `401`s - usually what you want.

> 💡 Need state inside your middleware - a database pool, an API-key set, the `AppState` from [Phase 4: Shared State](04-shared-state.md)? Use **`from_fn_with_state`** instead. You pass the state when building the layer, and your function takes a `State<T>` extractor as its first argument, exactly like a handler does.

## Why "it's all tower" is the real payoff

Step back and notice what you *didn't* learn: an axum-specific middleware API. There isn't one. `TraceLayer`, `TimeoutLayer`, `CorsLayer`, and your `from_fn` gate are all plain tower `Layer`s.

> 💡 Because they're tower, **the same layers work outside axum entirely.** A `TimeoutLayer` can wrap an HTTP *client* so outbound calls time out. A tracing or retry layer can wrap a gRPC service. The middleware skill you just built transfers to the whole tower ecosystem - that's the dividend of a framework that leans on a shared abstraction instead of inventing its own. The full `Service`/`Layer` story, including how to write a `Layer` by hand when `from_fn` isn't enough, is in [hyper & tower](/guides/hyper-and-tower).

## Recap

- **Middleware in axum is a tower `Layer` that wraps your whole service** - you add it with `.layer(...)` on the `Router`, like nesting dolls around your handler.
- **`tower-http`** ships the common layers - `TraceLayer` (request logging via `tracing`), `CorsLayer`, `TimeoutLayer`, compression - added with `cargo add tower-http --features ...` and dropped straight onto `.layer()`.
- **`ServiceBuilder`** groups multiple layers cleanly and, crucially, reads **top-to-bottom** (outermost first) - the opposite of bare chained `.layer()` calls, which read **bottom-up**.
- **`axum::middleware::from_fn`** turns an `async fn(Request, Next)` into custom middleware: return an `Err`/response to short-circuit, or `next.run(req).await` to continue. Use **`from_fn_with_state`** when it needs `State`.
- Because everything is tower, **the same layers work with HTTP clients, gRPC, and other tower services** - see [hyper & tower](/guides/hyper-and-tower).

## Quick check

```quiz
[
  {
    "q": "In axum, what IS middleware, mechanically?",
    "choices": ["A special hook registered on the Router", "A tower Layer that wraps your service, added with .layer()", "A macro applied to each handler", "A function listed in a middleware: config field"],
    "answer": 1,
    "explain": "axum has no dedicated middleware API - middleware is a tower Layer that wraps the whole service, attached via .layer() on the Router."
  },
  {
    "q": "You write `router.layer(a).layer(b)` with bare chained calls. Which layer runs FIRST on an incoming request?",
    "choices": ["a, because it's written first", "b, because the last .layer() is the outermost", "Neither - order is undefined", "Both run simultaneously"],
    "answer": 1,
    "explain": "With bare chained .layer() calls, layers apply bottom-up: the last one added (b) is outermost and runs first. ServiceBuilder flips this to read top-down."
  },
  {
    "q": "In a `from_fn` middleware, how do you reject a request so the handler never runs?",
    "choices": ["Call next.run(req).await as usual", "Return an Err (or a response) instead of calling next.run", "Throw a panic", "Return Ok(()) with no body"],
    "answer": 1,
    "explain": "Returning early - e.g. Err(StatusCode::UNAUTHORIZED) - short-circuits the chain; the response goes straight back out and next.run is never called, so the handler doesn't execute."
  }
]
```


---

# Building a REST API

This is the phase where the pieces click together. You've met every part already:
the `Router` and routing (Phase 2), `Path`/`Query` extractors (Phase 2),
`Json` in and out and `IntoResponse` (Phase 3), and the shared `AppState` store
(Phase 4). A REST API is nothing more than those four things, assembled - hold
this picture before any code.

> 📝 **Mental model:** a REST *resource* - here, books - is **five handlers over
> one shared store**. Each handler is an `async fn` whose arguments are extractors
> (`State` for the store, `Path` for an id, `Json` for a request body) and whose
> return value implements `IntoResponse` (a status code, some JSON, or both). The
> five map onto HTTP verbs: **list** (GET all), **show** (GET one), **create**
> (POST), **update** (PUT), **delete** (DELETE). That's the same shape you'd write
> in Gin, Express, or Spring - axum just expresses it through Rust's type system
> instead of decorators or annotations.

If you've built a CRUD endpoint in any language, you already know the *job*. The
rest of this phase is watching that job land in idiomatic axum.

## The store, recapped

We keep the in-memory store from Phase 4, and add one thing: a way to mint new
ids. When a client POSTs a new book it doesn't send an id - the server assigns
one. We'll keep a counter inside the same `Mutex` as the map, so a single lock
covers both reading the next id and inserting.

```rust
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use serde::{Deserialize, Serialize};

#[derive(Clone, Serialize)]
struct Book {
    id: u32,
    title: String,
    author: String,
}

// What the client sends to create a book - no id; the server assigns it.
#[derive(Deserialize)]
struct NewBook {
    title: String,
    author: String,
}

// One Mutex guards both the map and the id counter, so each handler
// takes exactly one lock.
struct Store {
    books: HashMap<u32, Book>,
    next_id: u32,
}

#[derive(Clone)]
struct AppState {
    store: Arc<Mutex<Store>>,
}
```

*What just happened:* `Book` derives `Serialize` so it can go out as JSON, and
`NewBook` derives `Deserialize` so it can come in as JSON. `Store` bundles the
map with a `next_id` counter, and `AppState` wraps it in `Arc<Mutex<...>>` for the
same reason as Phase 4: the clone axum makes per request must *share* the store,
not copy it. ⚠️ The `Mutex` lock is held only for the few lines inside each
handler - lock, read or mutate, let the guard drop, never across an `.await`.
Every read clones a `Book` *out* of the map, so JSON gets an owned value and the
lock releases immediately.

## The five handlers

Each handler is small. Read them as variations on one theme: take what you need
via extractors, touch the store under a brief lock, return a status plus (maybe)
JSON.

```rust
use axum::{
    Json,
    extract::{Path, State},
    http::StatusCode,
    response::IntoResponse,
};

// GET /books  → 200 with the full list
async fn list(State(state): State<AppState>) -> Json<Vec<Book>> {
    let store = state.store.lock().unwrap();
    Json(store.books.values().cloned().collect())
}

// GET /books/{id}  → 200 with one book, or 404
async fn show(
    State(state): State<AppState>,
    Path(id): Path<u32>,
) -> impl IntoResponse {
    let store = state.store.lock().unwrap();
    match store.books.get(&id) {
        Some(book) => (StatusCode::OK, Json(book.clone())).into_response(),
        None => StatusCode::NOT_FOUND.into_response(),
    }
}

// POST /books  → 201 with the created book (now carrying its id)
async fn create(
    State(state): State<AppState>,
    Json(new): Json<NewBook>,
) -> impl IntoResponse {
    let mut store = state.store.lock().unwrap();
    let id = store.next_id;
    store.next_id += 1;
    let book = Book { id, title: new.title, author: new.author };
    store.books.insert(id, book.clone());
    (StatusCode::CREATED, Json(book))
}

// PUT /books/{id}  → 200 with the updated book, or 404
async fn update(
    State(state): State<AppState>,
    Path(id): Path<u32>,
    Json(new): Json<NewBook>,
) -> impl IntoResponse {
    let mut store = state.store.lock().unwrap();
    match store.books.get_mut(&id) {
        Some(book) => {
            book.title = new.title;
            book.author = new.author;
            (StatusCode::OK, Json(book.clone())).into_response()
        }
        None => StatusCode::NOT_FOUND.into_response(),
    }
}

// DELETE /books/{id}  → 204 No Content, or 404
async fn remove(
    State(state): State<AppState>,
    Path(id): Path<u32>,
) -> impl IntoResponse {
    let mut store = state.store.lock().unwrap();
    match store.books.remove(&id) {
        Some(_) => StatusCode::NO_CONTENT,
        None => StatusCode::NOT_FOUND,
    }
}
```

*What just happened:* every handler follows the same recipe. `list` and `show`
only read, so they lock without `mut`; `create`, `update`, and `remove` mutate, so
they bind the guard `mut`. The body-consuming `Json` extractor always comes
**last** - that's why `create` and `update` put `State`/`Path` first. Return types
are where `IntoResponse` earns its keep: `list` returns a plain `Json<Vec<Book>>`
(a 200), while handlers with two outcomes return `impl IntoResponse` and use
`(StatusCode, Json<...>)` tuples - status *and* body in one value. Where a branch
returns only a status (the 404s, the 204), we call `.into_response()` so both
`match` arms share a concrete type. `create` builds the `Book` *after* taking the
lock so it can read and bump `next_id` atomically under that one lock.

> ⚠️ Notice how much of this code is the 404 plumbing - every `match` repeats the
> `None => StatusCode::NOT_FOUND` arm, and we sprinkle `.into_response()` to make
> branches line up. It works, but it's noisy. Phase 7 replaces all of it with a
> custom error type and the `?` operator, so a missing book becomes one short line.
> For now, see the pattern plainly; we clean it up next.

## Wiring the router

Five handlers, two routes, one `.with_state`. The collection path (`/books`)
carries GET and POST; the item path (`/books/{id}`) carries GET, PUT, and DELETE.

```rust
use axum::{Router, routing::get};

fn app(state: AppState) -> Router {
    Router::new()
        .route("/books", get(list).post(create))
        .route("/books/{id}", get(show).put(update).delete(remove))
        .with_state(state)
}

#[tokio::main]
async fn main() {
    let state = AppState {
        store: Arc::new(Mutex::new(Store { books: HashMap::new(), next_id: 1 })),
    };
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app(state)).await.unwrap();
}
```

*What just happened:* `get(list).post(create)` chains two method handlers onto the
same path - axum routes by verb, so GET and POST on `/books` reach different
functions. The `{id}` segment is a path parameter that feeds the `Path<u32>`
extractor in `show`, `update`, and `remove`. `.with_state(state)` hands every
handler the shared store; because we pulled the router into its own `app()`
function, Phase 8 can reuse it in tests without spinning up a real server.

## Driving it with curl

Start it (`cargo run`) and exercise each verb. The responses below show what the
handlers above produce.

```bash
# Create two books - note the 201 and the server-assigned id
curl -s -X POST localhost:3000/books \
  -H 'content-type: application/json' \
  -d '{"title":"The Rust Programming Language","author":"Klabnik & Nichols"}'
# {"id":1,"title":"The Rust Programming Language","author":"Klabnik & Nichols"}

curl -s -X POST localhost:3000/books \
  -H 'content-type: application/json' \
  -d '{"title":"Programming Rust","author":"Blandy & Orendorff"}'
# {"id":2,"title":"Programming Rust","author":"Blandy & Orendorff"}

# List them all
curl -s localhost:3000/books
# [{"id":1,...},{"id":2,...}]

# Show one
curl -s localhost:3000/books/1
# {"id":1,"title":"The Rust Programming Language","author":"Klabnik & Nichols"}

# Update it
curl -s -X PUT localhost:3000/books/1 \
  -H 'content-type: application/json' \
  -d '{"title":"The Rust Programming Language, 2nd Ed.","author":"Klabnik & Nichols"}'
# {"id":1,"title":"The Rust Programming Language, 2nd Ed.","author":"Klabnik & Nichols"}

# Delete it - 204, empty body
curl -s -i -X DELETE localhost:3000/books/1 | head -n 1
# HTTP/1.1 204 No Content

# Ask for a book that no longer exists - 404
curl -s -i localhost:3000/books/1 | head -n 1
# HTTP/1.1 404 Not Found
```

*What just happened:* the full lifecycle of a resource. POST returned `201` with
the created body (id and all), the reads came back `200`, DELETE returned a bodyless
`204`, and the follow-up GET on the deleted id returned `404` - exactly the status
codes the handlers chose. The `-i` flag prints the status line so you can see the
codes the JSON body alone wouldn't reveal.

> 💡 The `HashMap` store is a stand-in for a database. When you swap in `sqlx` or
> SeaORM (Phase 9), the handlers keep this exact shape - extractors in, status +
> JSON out - only the body changes: `store.books.get(&id)` becomes a `SELECT`,
> `insert` becomes an `INSERT`. The `State` already holds a `PgPool` instead of an
> `Arc<Mutex<...>>` (recall from Phase 4 that a pool is already `Clone`), so the
> wiring doesn't move. That stability is the payoff of the mental model: once the
> *shape* is right, the storage backend is a detail.

## Recap

- A REST resource is **five `async fn` handlers over one shared `State`**, mapped
  to verbs: list (GET all), show (GET one), create (POST), update (PUT), delete
  (DELETE).
- Handlers compose the extractors you already know - **`State`** for the store,
  **`Path<u32>`** for the id, **`Json<NewBook>`** for the body - with the
  body-consuming `Json` always **last**.
- Return **`(StatusCode, Json<...>)`** to send a status and a body together;
  return a bare `StatusCode` for empty responses; use **`impl IntoResponse`** and
  `.into_response()` when a handler has multiple outcome types.
- The store wraps the map and an **id counter** in one `Mutex`, so a handler takes
  a single brief lock; clone values **out** of the map and never hold a lock across
  an `.await`.
- The repetitive `404` and `.into_response()` plumbing is the verbose part - Phase
  7 collapses it with a custom error type and the `?` operator.
- Keeping the router in its own `app()` function lets Phase 8 test it without a
  live server, and swapping the store for a real database (Phase 9) leaves the
  handler shape untouched.

## Quick check

Lock these in before we tackle error handling.

```quiz
[
  {
    "q": "In the create handler, why does the Json<NewBook> extractor come last in the argument list?",
    "choices": [
      "Alphabetical ordering of extractor types",
      "axum allows only one body-consuming extractor, and it must be the final argument",
      "Json is slower, so it runs last for performance",
      "It's a stylistic preference with no effect"
    ],
    "answer": 1,
    "explain": "Json consumes the request body. axum permits exactly one body extractor and requires it to be the last argument, so non-body extractors like State and Path go first."
  },
  {
    "q": "What does returning (StatusCode::CREATED, Json(book)) from a handler produce?",
    "choices": [
      "A 200 response with no body",
      "A 201 response whose body is the book serialized as JSON",
      "A compile error - you can't return a tuple",
      "A 201 response with the book as a plain-text string"
    ],
    "answer": 1,
    "explain": "A (StatusCode, Json<T>) tuple implements IntoResponse: the status sets the response code and the Json part sets the JSON body. Here that's 201 Created with the new book."
  },
  {
    "q": "When a book id isn't in the store, what does the show handler return, and what makes both match arms type-check?",
    "choices": [
      "It panics; the arms type-check because panics are never values",
      "StatusCode::NOT_FOUND, and calling .into_response() on both arms gives them the same concrete type",
      "An empty Json([]) with status 200",
      "It returns Result::Err, which axum converts automatically"
    ],
    "answer": 1,
    "explain": "The None arm returns StatusCode::NOT_FOUND (404). Because the Some arm returns a tuple and the None arm a bare status, both call .into_response() so the function's two branches share one return type behind impl IntoResponse."
  }
]
```


---

# Error Handling

By Phase 6 the books API works, but the handlers smell. Every one that can fail
spells out its own failure inline: look up a book, and if it's missing, `return
StatusCode::NOT_FOUND`; parse something bad, and build a `(StatusCode::BAD_REQUEST,
"...")` tuple by hand. The happy path and sad path tangle together, and every
handler invents its own error shape.

Here's the thing other frameworks make hard and axum makes beautiful: in axum,
**an error is a return value, not an exception.** There's no `throw`, no
exception unwinding the stack, no global error handler you register and hope
fires. You make *one* type that knows how to turn itself into an HTTP response,
and your handlers just hand that type back. The language does the rest.

> 📝 **Mental model:** a handler returning `Result<T, E>` is a valid axum
> handler **as long as both `T` and `E` implement `IntoResponse`.** You already
> know `IntoResponse` from Phase 3. So define your own `AppError`, implement
> `IntoResponse` for it *once*, and every handler can return `Result<_,
> AppError>`. The `?` operator propagates failures for you, and axum renders
> whatever comes back - success *or* error - through the same machinery.

## One error type, one response shape

Start by naming the ways your API can fail. For the books service that's a small
set: the book doesn't exist, the client sent something invalid, or something
broke on our end. That's an enum.

```rust
use axum::{
    Json,
    http::StatusCode,
    response::{IntoResponse, Response},
};

enum AppError {
    NotFound,
    BadRequest(String),
    Internal,
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, msg) = match self {
            AppError::NotFound => (StatusCode::NOT_FOUND, "not found".to_string()),
            AppError::BadRequest(m) => (StatusCode::BAD_REQUEST, m),
            AppError::Internal => {
                (StatusCode::INTERNAL_SERVER_ERROR, "internal error".to_string())
            }
        };
        (status, Json(serde_json::json!({ "error": msg }))).into_response()
    }
}
```

*What just happened:* `AppError` enumerates the failure modes, with
`BadRequest(String)` carrying a message so callers know *what* was wrong. The
`impl IntoResponse` is the whole trick - the single place that decides how an
error becomes HTTP. We `match` the variant into a `(status, message)` pair, then
build the response from a tuple: `(StatusCode, Json<...>)` already implements
`IntoResponse` (Phase 3), so wrapping the message in `serde_json::json!` gives
every error the same JSON envelope - `{"error": "..."}`. Change that shape here,
once, and every endpoint's errors change with it.

## Rewriting the handlers with `?`

Now the payoff. Compare the Phase 6 style - manual `StatusCode` returns - with
what `AppError` lets you write. Here's a `show` handler, before:

```rust
// Phase 6 style: failure handling tangled into the handler.
async fn show(
    State(state): State<AppState>,
    Path(id): Path<u32>,
) -> Result<Json<Book>, StatusCode> {
    let books = state.books.lock().unwrap();
    match books.get(&id) {
        Some(book) => Ok(Json(book.clone())),
        None => Err(StatusCode::NOT_FOUND),
    }
}
```

And after, with `AppError` and `?`:

```rust
use axum::extract::{Path, State};

async fn show(
    State(state): State<AppState>,
    Path(id): Path<u32>,
) -> Result<Json<Book>, AppError> {
    let books = state.books.lock().unwrap();
    let book = books.get(&id).cloned().ok_or(AppError::NotFound)?;
    Ok(Json(book))
}
```

*What just happened:* the `match` collapsed into one line. `books.get(&id)`
returns an `Option<&Book>`; `.cloned()` turns it into `Option<Book>`; and
`.ok_or(AppError::NotFound)` converts that into a `Result<Book, AppError>` - 
`Some` becomes `Ok`, `None` becomes `Err(AppError::NotFound)`. `?` then says "if
this is an `Err`, return it right now; otherwise unwrap the `Ok`." Because
`AppError: IntoResponse`, that `Err` is a complete, valid response - axum
renders it as our `404` JSON. The handler now reads as the happy path with
failure points marked by `?`.

Validation gets the same treatment. Suppose creating a book requires a non-empty
title:

```rust
async fn create(
    State(state): State<AppState>,
    Json(book): Json<Book>,
) -> Result<StatusCode, AppError> {
    if book.title.trim().is_empty() {
        return Err(AppError::BadRequest("title must not be empty".into()));
    }
    state.books.lock().unwrap().insert(book.id, book);
    Ok(StatusCode::CREATED)
}
```

*What just happened:* a plain `return Err(...)` short-circuits with a `400` and
our message; the success path returns `201 Created`. Both arms are values of the
same `Result<StatusCode, AppError>`, both implement `IntoResponse`, so axum
handles either without you wiring up anything extra. The error *is* the return
value.

## Folding foreign errors in with `From`

The `?` operator has a second superpower you haven't used yet: it doesn't just
propagate an error, it *converts* it. When you write `something?` and the error
type doesn't match your function's error type, Rust looks for a `From` impl to
bridge them. That's how you let `?` swallow errors from libraries - a database
driver, a JSON parser - that know nothing about your `AppError`.

Say a future version of the books API talks to a real database via `sqlx`. Its
calls return `Result<_, sqlx::Error>`. Teach `AppError` how to absorb that:

```rust
impl From<sqlx::Error> for AppError {
    fn from(err: sqlx::Error) -> Self {
        match err {
            sqlx::Error::RowNotFound => AppError::NotFound,
            other => {
                tracing::error!("db error: {other}");
                AppError::Internal
            }
        }
    }
}
```

*What just happened:* this `From<sqlx::Error>` impl maps a missing row to our
`NotFound` and treats every other database failure as `Internal` - logging the
real cause with `tracing` while sending the client only a generic `500`. Now a
handler can use `?` directly on a `sqlx` call:

```rust
async fn show_db(
    State(state): State<AppState>,
    Path(id): Path<u32>,
) -> Result<Json<Book>, AppError> {
    let book = sqlx::query_as::<_, Book>("SELECT * FROM books WHERE id = $1")
        .bind(id)
        .fetch_one(&state.db)
        .await?; // sqlx::Error -> AppError, automatically
    Ok(Json(book))
}
```

*What just happened:* `fetch_one` yields `Result<Book, sqlx::Error>`, but the
function returns `Result<_, AppError>`. The `?` sees the mismatch, finds your
`From<sqlx::Error> for AppError`, and converts on the way out - a `RowNotFound`
becomes a clean `404`, anything else a logged `500`. One `?` does propagation
*and* conversion *and* the correct status code, with zero boilerplate in the
handler.

> 💡 Writing `impl IntoResponse` and a pile of `From` impls by hand gets old. The
> **`thiserror`** crate generates them from a derive: annotate each variant with a
> `#[from]` and a display message and it writes the `From`/`Display` impls for
> you, leaving just the `IntoResponse`. For quick app code that doesn't need a
> typed enum, **`anyhow`** gives you one catch-all error type (`anyhow::Error`)
> and an ergonomic `?` everywhere. Reach for `thiserror` when callers need to
> distinguish variants; reach for `anyhow` when they don't.

## Let the framework handle what it already handles

One trap worth naming: don't reinvent error handling axum already does for you.

⚠️ axum's built-in extractors reject bad input *before your handler runs*, with
sensible defaults. Send a malformed JSON body to a handler taking `Json<Book>`
and you get a `400 Bad Request` with a useful message, unwritten by you. Same
for a missing path segment, a bad `Query`, an oversized body. You *can*
customize these rejections, but the defaults are good - only override them when
you genuinely need a different shape.

⚠️ And the cardinal rule: **don't panic in a handler - return an error.** A
`.unwrap()` on a failing `Result`, an out-of-bounds index, an `expect()` that
fires - these unwind the task instead of producing a tidy response. axum
catches it and returns a bare `500`, but you've lost the chance to log context,
choose a status, or shape the body. Every fallible step should be a `?` into
your `AppError`, not a panic. (The one `.unwrap()` surviving in this guide is
`state.books.lock().unwrap()` - a poisoned `Mutex` means another thread already
panicked holding it, so the process is arguably doomed anyway.)

> 💡 The shape to keep in your head: **extractor rejections** guard the door
> (bad input never reaches you), **`?` with `AppError`** handles everything your
> logic can hit, and **panics are bugs**, not a control-flow tool. Get those
> three straight and your error handling is both correct and almost invisible.

## Recap

- In axum an **error is a return value, not an exception**: a handler returning
  `Result<T, E>` is valid whenever **both `T` and `E` implement `IntoResponse`**.
- Define **one `AppError` enum** and implement **`IntoResponse` for it once** so
  every endpoint shares a single JSON error shape - change it in one place.
- Handlers return `Result<_, AppError>` and use **`?`** with helpers like
  **`.ok_or(AppError::NotFound)`**, collapsing tangled `match`es into the happy
  path with marked failure points.
- The **`?` operator also converts**: an **`impl From<E> for AppError`** lets `?`
  fold foreign errors (e.g. `sqlx::Error`) into your type - mapping to the right
  status and logging the real cause while hiding internals.
- Use **`thiserror`** to derive the `From`/`Display` boilerplate, or **`anyhow`**
  for a catch-all app error when callers don't need to distinguish variants.
- Lean on axum's **built-in extractor rejections** for bad input, and **never
  panic in a handler** - return an error so you control the status, body, and
  logs.

## Quick check

Lock in the error model before moving on to testing and production.

```quiz
[
  {
    "q": "What makes a handler returning Result<Json<Book>, AppError> a valid axum handler?",
    "choices": [
      "AppError is registered in a global error handler",
      "Both Json<Book> and AppError implement IntoResponse",
      "The handler is wrapped in a try/catch layer",
      "AppError derives Clone"
    ],
    "answer": 1,
    "explain": "axum accepts any Result handler as long as both the Ok type and the Err type implement IntoResponse - then it renders whichever one is returned."
  },
  {
    "q": "Why does `let b = books.get(&id).cloned().ok_or(AppError::NotFound)?;` work?",
    "choices": [
      "ok_or turns the Option into a Result, and ? returns the Err (an IntoResponse) or unwraps the Ok",
      "? catches a panic raised by get()",
      "ok_or logs the error and returns 200 anyway",
      "It only compiles if AppError implements Clone"
    ],
    "answer": 0,
    "explain": "ok_or maps None to Err(AppError::NotFound); ? then early-returns that Err (which is an IntoResponse, so a complete response) or unwraps the Some."
  },
  {
    "q": "How does `?` let a handler call a sqlx function that returns sqlx::Error and still return AppError?",
    "choices": [
      "sqlx::Error and AppError are the same type",
      "? silently discards the sqlx error and returns Internal",
      "An impl From<sqlx::Error> for AppError lets ? convert the error as it propagates",
      "axum auto-converts any error into AppError"
    ],
    "answer": 2,
    "explain": "When the error types differ, ? looks for a From impl. Implementing From<sqlx::Error> for AppError makes ? convert and propagate in one step."
  }
]
```


---

# Testing & Production

You've grown the books API from a single route into a real REST service - extractors, shared state, tower middleware, CRUD, an error type that turns into responses. Now comes the part that decides whether anyone *trusts* it: proving it works, and running it somewhere real without falling over at 3am during a deploy. Both turn out to be small, once you see the one fact that makes them small.

## The mental model: your `Router` is a `tower::Service`, so testing is calling it in memory

Here's the fact that makes axum a joy to test. A `Router` **is a `tower::Service`** - the same abstraction every tower layer speaks. A service is, at heart, a thing you hand a `Request` and get back a `Response`, and your whole app is one of those.

> 💡 If the router is a service, a test is nothing more than handing it a request and awaiting the response - directly, in your own process. No network, no port, no `tokio::spawn` running a server in the background. You build a fake request, the router runs the *entire* chain (middleware, routing, extractors, your handler), and you read the response back - all in memory, in microseconds.

The tool is `oneshot`, an extension method from `tower::ServiceExt`: it takes ownership of the service, drives one request through it, and gives you the response.

```bash
cargo add tower --features util
```

The `util` feature brings in `ServiceExt` (and `.oneshot`). A test for `GET /books`:

```rust
use tower::ServiceExt; // brings .oneshot into scope
use axum::{body::Body, http::{Request, StatusCode}};

#[tokio::test]
async fn list_books_ok() {
    let app = app(); // your Router-building fn - see the next section

    let res = app
        .oneshot(Request::builder().uri("/books").body(Body::empty()).unwrap())
        .await
        .unwrap();

    assert_eq!(res.status(), StatusCode::OK);
}
```

*What just happened:* `#[tokio::test]` gives the test an async runtime, like `#[tokio::main]` does for `main`. We built the same router the real app uses, then `oneshot` drove a hand-built `GET /books` request through it - `body(Body::empty())` because a GET needs none. `res` comes back a real `Response`; `res.status()` is whatever your handler set. Runs in under a millisecond, no socket opened. `oneshot` *consumes* `app`, which is fine since each test builds its own.

Testing a **POST** with a JSON body is the same shape with two additions: attach the body and set the `Content-Type` header so axum's `Json` extractor knows to parse it.

```rust
#[tokio::test]
async fn create_book_returns_201() {
    let app = app();

    let body = r#"{"title":"Dune","author":"Herbert"}"#;
    let req = Request::builder()
        .method("POST")
        .uri("/books")
        .header("content-type", "application/json")
        .body(Body::from(body))
        .unwrap();

    let res = app.oneshot(req).await.unwrap();
    assert_eq!(res.status(), StatusCode::CREATED);
}
```

*What just happened:* method `POST`, URI `/books`, and - this part matters - the `content-type` header set to `application/json`. Without it, `Json<T>` rejects the request before your handler runs, and you'd be testing the wrong path (`415`, not your create logic). `Body::from(body)` becomes the request body; we assert `201 Created`. For inputs you *expect* to fail, send the bad payload and assert the `400`/`422` your `AppError` produces (Phase 7).

To check the *body* of a response, not the status, you pull the bytes out. axum's body is a stream, so you collect it with `to_bytes`:

```rust
use axum::body::to_bytes;

let bytes = to_bytes(res.into_body(), usize::MAX).await.unwrap();
let book: Book = serde_json::from_slice(&bytes).unwrap();
assert_eq!(book.title, "Dune");
```

*What just happened:* `res.into_body()` takes ownership of the response body, and `to_bytes` reads the whole stream into a byte buffer. The second argument is a size cap - `usize::MAX` means "no limit," fine in a test where you control the input, but you'd pass a real ceiling in request-handling code so a giant body can't exhaust memory. `serde_json::from_slice` deserializes those bytes into your `Book` struct, and you assert on real fields - a full round trip: request in, typed value out, no network in sight.

That's the heart of testing an axum app. The rest - table-driven cases, fixtures, running it on every push - is general Rust testing, covered in [testing in CI](/guides/testing-in-ci).

## Structure: one `fn app() -> Router` that `main` and tests both build

Every test above started with `let app = app();`. That's the structural move that makes this clean: **factor router construction into one function** returning the fully-wired `Router`. Both `main` and your tests call it, so they exercise the *exact same* routing, middleware, and state.

```rust
use axum::{routing::{get, post}, Router};

fn app() -> Router {
    Router::new()
        .route("/books", get(list_books).post(create_book))
        .route("/books/{id}", get(get_book).delete(delete_book))
        .layer(tower_http::trace::TraceLayer::new_for_http())
}

#[tokio::main]
async fn main() {
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app()).await.unwrap();
}
```

*What just happened:* all route registration and layering lives in one place. `main` binds a TCP listener and serves `app()`; a test builds the *same* `app()` and drives it with `oneshot`. There's no second, slightly-different set of routes that quietly drifts out of sync - one source of truth. The moment you find yourself copy-pasting route setup into a test, stop and pull out an `app()`.

> 📝 Handlers needing shared state - a database pool, config (Phase 4) - make it `fn app(state: AppState) -> Router` ending in `.with_state(state)`. `main` builds the real pool; tests pass a fake or in-memory one.

## Graceful shutdown: drain in-flight requests instead of dropping them

`axum::serve(listener, app()).await` runs forever - until the process is killed. For learning that's perfect. For a real deploy it has a gap: when your platform restarts the service (a deploy, a scale-down, a `SIGTERM`), the process is cut off mid-flight and clients see broken connections.

What you want instead is a **graceful shutdown**: on a shutdown signal, *stop accepting new connections, finish the requests already in progress, then exit.* axum has this built in - hand `serve` a future that resolves when it's time to stop:

```rust
use tokio::signal;

#[tokio::main]
async fn main() {
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();

    axum::serve(listener, app())
        .with_graceful_shutdown(shutdown_signal())
        .await
        .unwrap();
}

async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c().await.expect("failed to install Ctrl-C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install SIGTERM handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }
}
```

*What just happened:* `with_graceful_shutdown` takes a future - `shutdown_signal()` - and watches it while serving. The moment that future resolves, axum stops accepting new connections and waits for in-flight requests to drain before `serve` returns. Inside `shutdown_signal`, we build two futures: one completing on Ctrl-C (`SIGINT`, what you press locally), one on `SIGTERM` (what orchestrators like Kubernetes send on a restart). The `#[cfg(unix)]` pair handles `SIGTERM` not existing on Windows - there, `terminate` never resolves, so only Ctrl-C triggers shutdown. `tokio::select!` waits for *whichever* fires first and starts the drain.

## Deploy shape: release binary, tiny container, env config, real logs

Rust ships like Go: compile to a **single native binary**, no interpreter, no `node_modules`, no virtualenv. Build the optimized version:

```bash
cargo build --release
```

*What just happened:* `--release` turns on optimizations (and off debug assertions), producing a binary in `target/release/` dramatically faster than the debug build. It compiles slower - that's the trade - so keep using plain `cargo run` for development and only build `--release` for what you ship.

Read configuration - at minimum the **port** and your **database URL** - from the environment, not hard-coded constants. This is the 12-factor convention, and it's what platforms expect:

```rust
let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string());
let addr = format!("0.0.0.0:{port}");
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
```

*What just happened:* we read `PORT` from the environment and fall back to `3000` for local dev, so the *same binary* runs unchanged whether it's on your laptop or a platform that injects `PORT=10000`. Bind to `0.0.0.0`, not `127.0.0.1` - inside a container, `127.0.0.1` is only reachable from within the container, so nothing outside can connect. The same principle applies to `DATABASE_URL` and any secrets: configuration comes from the environment so the artifact stays identical across environments.

A minimal **multi-stage Dockerfile** compiles the binary in one stage and copies *only* it into a near-empty final image:

```bash
# Build stage - has the full Rust toolchain
FROM rust:1 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release

# Run stage - distroless: no shell, no package manager, just enough to run a binary
FROM gcr.io/distroless/cc-debian12
COPY --from=builder /app/target/release/books-api /usr/local/bin/books-api
EXPOSE 3000
ENV PORT=3000
ENTRYPOINT ["/usr/local/bin/books-api"]
```

*What just happened:* the first stage has the whole Rust toolchain and compiles the binary; the second is a `distroless/cc` image - just the C runtime your binary dynamically links against, no shell, no package manager - and we copy in only the one executable. (`cc-debian12` rather than `static` because a default `cargo build` links libc dynamically.) The result is a container measured in tens of megabytes with a tiny attack surface - no shell means no shell for an attacker to drop into.

One easy-to-miss detail: **your `TraceLayer` logs nothing until you install a subscriber.** The `tracing` ecosystem separates *emitting* events (what `TraceLayer` does) from *printing* them (what a subscriber does). Add `tracing-subscriber` and initialize it once at startup:

```rust
fn main() {
    tracing_subscriber::fmt::init();
    // ...build runtime / serve...
}
```

*What just happened:* `tracing_subscriber::fmt::init()` installs a subscriber that formats events and writes them to stdout, honoring the `RUST_LOG` env var (e.g. `RUST_LOG=info`) for level filtering. Without this one line, the `TraceLayer` from Phase 5 emits per-request spans into the void and you see nothing. With it, requests show up in your logs - and in a container, stdout is exactly where your platform collects them.

Finally, put a **reverse proxy** in front - nginx, Caddy, or whatever your platform provides. It terminates TLS, can serve static assets, and load-balances across instances of your binary. Your axum app speaks plain HTTP on its port; the proxy faces the public internet - you generally don't terminate TLS in axum itself.

That's the whole deploy shape: one release binary, configured by env vars, in a small container, logging to stdout, behind a proxy. Taking it the rest of the way to a live URL - picking a host, wiring CI, the domain and TLS specifics - is covered in [ship your side project](/guides/ship-your-side-project).

## Recap

- A `Router` **is a `tower::Service`**, so you **test it in memory** with no network: add `tower` with the `util` feature, bring `tower::ServiceExt` into scope, and `app.oneshot(request).await` drives one request through the entire chain. Assert on `res.status()`; read the body with `axum::body::to_bytes(res.into_body(), usize::MAX)`.
- For a POST, set `method`, the `content-type: application/json` header (or the `Json` extractor rejects it), and a `Body::from(json)`.
- Factor router construction into one `fn app() -> Router` (or `fn app(state) -> Router`) that both `main` and tests build - one source of truth, no drift.
- Add a **graceful shutdown** with `axum::serve(listener, app()).with_graceful_shutdown(shutdown_signal())`, where `shutdown_signal()` uses `tokio::signal` to await Ctrl-C / `SIGTERM` and `tokio::select!` to fire on whichever comes first.
- Ship a `cargo build --release` binary in a small multi-stage container (builder → `distroless/cc`), read `PORT`/`DATABASE_URL` from the environment, call `tracing_subscriber::fmt::init()` so `TraceLayer` actually logs, and put a reverse proxy in front for TLS and load balancing.

## Quick check

Lock in the core fact (the router is a service) and the two production must-haves:

```quiz
[
  {
    "q": "Why can you test an axum router with oneshot and no real network?",
    "choices": ["axum spins up a hidden test server on a random port", "A Router is a tower::Service, so a test hands it a Request and awaits the Response directly in-process", "oneshot mocks the TCP stack at the OS level", "You can't - axum tests always need a running server"],
    "answer": 1,
    "explain": "A Router is a tower::Service. ServiceExt::oneshot drives a single hand-built Request through the entire middleware-and-handler chain in memory and returns the Response - nothing touches a socket."
  },
  {
    "q": "When testing a POST that uses the Json extractor, what must you set on the request besides the body?",
    "choices": ["Nothing - Json parses any body", "The content-type: application/json header, or the Json extractor rejects the request before your handler runs", "A Content-Length header you compute by hand", "An Authorization header"],
    "answer": 1,
    "explain": "Without content-type: application/json, the Json<T> extractor rejects the request (415) before your handler executes, so you'd be testing the wrong path. Set the header so the body is parsed as JSON."
  },
  {
    "q": "What does with_graceful_shutdown(shutdown_signal()) give you over a plain axum::serve(...).await?",
    "choices": ["Faster request handling", "On Ctrl-C / SIGTERM it stops accepting new connections and drains in-flight requests before exiting, instead of being cut off mid-flight", "Automatic TLS termination", "It restarts the server on panic"],
    "answer": 1,
    "explain": "with_graceful_shutdown watches a future (built from tokio::signal for Ctrl-C and SIGTERM). When it resolves, axum stops accepting connections and lets in-progress requests finish, so a deploy or restart doesn't sever live requests."
  }
]
```


---

# Where to Go Next

Stop and look at what you can actually do now. Stand up an axum server on Tokio, route a request to a handler, pull pieces out of it with extractors like `Path`, `Query`, `Json`, and `State`, return any type that implements `IntoResponse`, share a store across handlers without a global, wrap the whole thing in tower middleware, build full CRUD, handle errors with a custom type and `?`, and test it with `oneshot` before shipping with graceful shutdown. That's a real REST API, not a toy.

Here's the quieter win: because axum leans on Rust's type system instead of macros, you didn't only learn a framework - you learned how one fits together. A **`Router`** maps paths to handlers. A handler is **an `async fn` whose arguments extract from the request and whose return value becomes the response.** Tower **layers** wrap it. No hidden magic to memorize - when something breaks at 2am, you can reason your way out from the compiler's complaints.

So this last phase is the map: where axum sits among the other Rust web frameworks, the layer you'll almost certainly add next, the roots worth learning, and one concrete thing to build.

## axum vs the field

You now know enough to choose a framework *on purpose* rather than by reputation. The good news in Rust: the big three are all production-grade and all fast. The differences are about *feel* and *what you build on*, not whole different universes.

```mermaid
flowchart TD
  Start[Need a Rust web service?] --> Q{What do you value most?}
  Q -- Tower ecosystem + type-safe extractors --> Axum[axum]
  Q -- Max performance + maturity --> Actix[actix-web]
  Q -- Most ergonomic, macro-driven --> Rocket[Rocket]
  Axum --> Note[axum runs this very site]
```

A line on each:

- **axum** - the modern default, from the Tokio team. Tower-native and type-system-driven: plain `async fn` handlers, extractors as arguments, `IntoResponse` returns. Its quiet superpower is the **tower** ecosystem - middleware you write for axum is reusable, and gRPC via **tonic** shares the same `Service` abstraction. This is the framework serving the page you're reading.
- **actix-web** - the mature, batteries-included heavyweight, consistently at or near the **top of the performance benchmarks**, with its own actor-flavored history and a deep feature set. Pick it when raw throughput and a long track record matter most. See [actix-web From Zero](/guides/actix-web-from-zero).
- **Rocket** - the most **ergonomic** of the three, leaning hard on macros (`#[get("/")]` and friends) for wonderfully concise handler code. Pick it for the least ceremony and the most readable routes. See [Rocket From Zero](/guides/rocket-from-zero).

📝 None of these is "the best" - they're aimed at slightly different priorities, and all three will happily run a serious service. The senior instinct isn't memorizing a winner, it's asking "best for *this* job?" You have the pieces for that now.

## The layer you'll add next: a real database

Every API in this guide kept its books in memory - perfect for learning, useless in production, since restarting the server loses the data. The next thing almost every real axum service grows is a **database**.

Rust has **no single default ORM** the way some ecosystems do. Three common answers, each with a clear personality:

- **`sqlx`** - not an ORM at all, but the most popular companion to axum. You write **raw SQL**, and a macro checks your queries **against a real database at compile time**, so a typo'd column name is a build error, not a 500 in production. Fully async.
- **SeaORM** - a proper **async ORM** built on top of sqlx, for entities, relations, and a query builder instead of hand-written SQL.
- **Diesel** - the **mature, established** ORM, with a rich type-safe query DSL. More sync-flavored in its roots, worth knowing if everything else in your stack is async.

The reassuring bit: your handlers barely change. Recall Phase 4, where you put a store into `State` with `with_state` and pulled it out via `State<T>` - that investment pays off here. A **`sqlx::PgPool`** drops straight into the same `State` slot. Handlers still extract the pool, run a query, and return something that implements `IntoResponse`. You're swapping the bottom layer, not rewriting the top.

## The roots: tokio, hyper, and tower

axum is small because it stands on three things you've been using all along, sometimes without naming them. Learning those roots removes the *last* of the magic.

- **Tokio** is the async runtime - it drives your `async fn`s, schedules tasks, handles the I/O. Every `.await` in your handlers ultimately answers to it. See [Tokio: The Async Runtime](/guides/tokio-the-async-runtime).
- **hyper** is the HTTP implementation axum is built on; **tower** is the universal middleware abstraction - the `Service` and `Layer` traits behind every layer you added in Phase 5. See [hyper & tower](/guides/hyper-and-tower).

You don't need these to ship. But the day you want to understand *why* an extractor works, or write a tower layer no crate offers, these two guides are where the floor drops away.

## What to build

Reading more won't make this stick. Building one real thing will - here's the assignment, deliberately concrete.

Take the **books API** you grew across this guide and carry it all the way home:

- **Swap the in-memory store for sqlx + Postgres** so the books survive a restart. Drop a `PgPool` into `State`, write a few compile-checked queries, and watch your handlers stay almost exactly as they were.
- **Add JWT (or session) auth middleware** so each request proves who it is, and books belong to a user. This is the tower middleware pattern from Phase 5, aimed at a real job.
- **Wire up `tracing`** for observability, so you can actually see what your service is doing under load (the `tower-http` trace layer you met in Phase 5 plugs straight in).
- **Generate API docs** with OpenAPI via **utoipa**, so other people - and future you - can read the contract.
- **Tidy up config** so secrets, the database URL, and the port come from the environment, not hardcoded values.
- **Deploy it** somewhere you can hit from your phone, with the graceful shutdown from Phase 8 wired up.

If the books API feels too familiar, build something small and new end to end instead - a **URL shortener** or a **notes API**. Same muscles: routes, extractors, state, layers, errors, tests, deploy. Finishing one project completely teaches more than three more tutorials would.

## The clear-eyed close

axum was never magic. Strip it back and it's a handful of ideas you now understand completely: a **`Router`** that sends a request to an **`async fn` whose arguments extract from it and whose return value becomes the response**, wrapped in **tower layers** - plain Rust, checked by the compiler, sitting on tokio and hyper.

That's why you can read the machine now, and reason about it when it misbehaves. Go finish the books API, give it a real database, lock it behind auth, light it up with tracing, deploy it, and show someone. You're ready.

## Recap

1. **You can ship a real axum API** - routed, extracted, responded, state-shared, layered with tower, error-handled, tested, and deployed - and you understand *why* each piece works, because axum hides nothing behind macros.
2. **Choose a framework on purpose** - axum for the tower ecosystem and type-safe extractors (this site runs on it), actix-web for maximum performance and maturity, Rocket for concise, ergonomic macro-driven code.
3. **A database is the next layer, and Rust has no single default** - sqlx (compile-checked raw SQL), SeaORM (async ORM), or Diesel (mature ORM). A `sqlx::PgPool` drops right into the `State` slot from Phase 4, so your handlers barely change.
4. **Learn the roots to remove the last magic** - tokio (the runtime), hyper (the HTTP layer), and tower (the `Service`/`Layer` middleware abstraction your layers were built on).
5. **Build and finish one thing** - carry the books API to sqlx + Postgres, JWT auth, tracing, OpenAPI docs, real config, and a deploy. Or build a small URL shortener / notes API end to end.

## Quick check

Three decisions to take with you as you leave this guide:

```quiz
[
  {
    "q": "You want type-safe extractors and a reusable middleware ecosystem, with a clean path to gRPC via tonic later. Which framework fits on purpose?",
    "choices": [
      "Rocket, because it uses the most macros",
      "axum, which is tower-native with type-system-driven extractors",
      "actix-web, because it's the fastest",
      "None of them support middleware"
    ],
    "answer": 1,
    "explain": "axum is tower-native and driven by the type system, so its middleware is reusable and tonic shares the same tower Service abstraction. Pick actix-web for max performance, Rocket for concise macro-driven code."
  },
  {
    "q": "What is the real situation with ORMs in Rust for an axum API?",
    "choices": [
      "axum ships its own official ORM you must use",
      "There's no single default - sqlx (compile-checked raw SQL), SeaORM (async ORM), and Diesel (mature ORM) are the common choices",
      "Only Diesel works with async Rust",
      "Rust web apps can't use a database"
    ],
    "answer": 1,
    "explain": "Rust has no single default ORM. sqlx checks raw SQL at compile time, SeaORM is an async ORM on top of it, and Diesel is the mature, more sync-flavored ORM. You choose based on the job."
  },
  {
    "q": "You're swapping the in-memory store for sqlx + Postgres. Why do your handlers barely change?",
    "choices": [
      "Because axum rewrites handlers automatically when you add a database",
      "Because a sqlx::PgPool drops into the same State slot from Phase 4, so handlers still extract it, query, and return an IntoResponse",
      "Because you have to abandon State and use globals instead",
      "They don't - every handler must be rewritten from scratch"
    ],
    "answer": 1,
    "explain": "Phase 4's State pattern pays off: a PgPool goes into State just like the in-memory store did. Handlers still extract the pool with State<T>, run a query, and return something that implements IntoResponse - the bottom layer changes, the top stays."
  }
]
```
