# Rocket From Zero

> Learn Rust's most ergonomic web framework: attribute-route handlers and launch, dynamic paths, request guards and data, responders, managed state and fairings, a full REST API with error catchers, and testing and config. The framework that makes Rust web code read almost like Flask - powered by macros.


---

# Rocket From Zero

Rocket is the Rust web framework that prizes ergonomics above all. Where axum and actix-web ask you to
assemble routers and handlers, Rocket lets you write `#[get("/books/<id>")]` above a function and be done - 
the framework reads your attributes and function signature and wires everything up. It leans hard on Rust
**macros** to do that, which is the trade: the code is wonderfully concise and readable (close to what a
Flask or Express developer expects), at the cost of more "magic" happening behind the attributes. If you've
found other Rust frameworks verbose, Rocket is the antidote.

The mental model is "attributes describe routes, the function signature describes inputs." A handler is a
function annotated with **`#[get(...)]`/`#[post(...)]`**; its **parameters are pulled from the path,
query, body, and *request guards*** (types that must succeed for the handler to run, like an authenticated
user); and its **return type is a `Responder`**. You register handlers with `routes![...]` and start with
`#[launch]`. Shared dependencies are **managed state**, and **fairings** are Rocket's middleware. Hold
"the attribute is the route, the signature is the request," and Rocket's magic becomes legible.

> 📝 This teaches the **framework** - it assumes you know **Rust** (ownership, traits, `Result`,
> and a comfort with how attribute macros feel - [Rust From Zero](/guides/rust-from-zero)). Compare it
> with [axum](/guides/axum-from-zero) and [actix-web](/guides/actix-web-from-zero) to see the
> macros-vs-builders split; all three sit on async Rust ([Tokio](/guides/tokio-the-async-runtime)).
> Rocket 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 attribute route to a tested,
configured REST API. Phases carry difficulty badges.

## The phases

**Part 1 - The core (🟢 Basic)**
1. **[What Rocket Is & Your First Server](01-what-rocket-is.md)** 🟢 - `#[get]`, `routes!`, `#[launch]`, and a running server.
2. **[Routing & Dynamic Paths](02-routing-and-paths.md)** 🟢 - path segments `<id>`, query params, and `FromParam`.
3. **[Request Guards & Data](03-guards-and-data.md)** 🟡 - `Json<T>` data, forms, and request guards (the auth pattern).

**Part 2 - A real API (🟡 → 🔴)**
4. **[Responders](04-responders.md)** 🟡 - `Json`, status, custom responders, and returning `Result`.
5. **[Managed State & Fairings](05-state-and-fairings.md)** 🔴 - `State<T>`, `.manage(...)`, and fairings as middleware.
6. **[A REST API with Error Catchers](06-rest-api-and-catchers.md)** 🔴 - full CRUD plus `#[catch(404)]` error catchers.

**Part 3 - Ship it (🟡 → 🟢)**
7. **[Testing & Configuration](07-testing-and-config.md)** 🟡 - the local test client and `Rocket.toml`.
8. **[Where to Go Next](08-where-to-go-next.md)** 🟢 - Rocket vs axum/actix, data layers, and what to build.

> The throughline: an **attribute is the route**, the **function signature is the request** (params +
> guards), the **return type is the response**, and macros wire it together. Concise Rust web code, with
> the magic now explained.


---

# What Rocket Is & Your First Server

You know [Rust](/guides/rust-from-zero) - ownership, traits, `Result`, and the slightly
uncanny feeling of an attribute macro doing work above your function. Rust web code has a
reputation for verbosity: routers assembled by hand, handler signatures spelled out, types
threaded through builders. Rocket's answer: write `#[get("/")]` over a function, and you're done.

Rocket's whole personality is **ergonomics** - it wants your web code to read almost like Flask
or Express, even though it's Rust underneath. It gets there by leaning hard on **attribute
macros**: annotate a function with a route attribute, list your functions in `routes![...]`,
and Rocket reads those attributes and signatures and wires the whole thing together. The payoff
is concise, readable code. The price is "magic" - behavior that isn't visible on the page. This
guide makes that magic legible, one piece at a time.

## The ergonomic trade

📝 **Rocket** - a Rust web framework built around *attribute-route handlers*. You write a
normal function, put a route attribute like `#[get("/")]` above it, and that function becomes
the code that runs for that path. Routing, request parsing, and response building are all
inferred from the attribute and the function's signature.

Its sibling Rust frameworks mostly favor a *builder* style instead, assembling a router
explicitly in code:

- [**axum**](/guides/axum-from-zero) builds a `Router` and attaches handlers with method calls
  like `.route("/", get(handler))` - ordinary Rust functions and values, no custom attributes.
- [**actix-web**](/guides/actix-web-from-zero) is similar in spirit: register services and
  routes on an `App` builder, with a focus on raw throughput.
- **Rocket** is *attribute-first*: the route lives in an attribute above the function, not in a
  builder call elsewhere. Less wiring on the page, more inference under it.

💡 Attributes give you the most concise, scannable web code in the Rust ecosystem - but they
also hide machinery. When something doesn't compile, the error can point at a macro-generated
thing you never wrote. The fix is a clear mental model of what each macro *does*, which is what
we're building here. Want maximum explicitness with no macro magic? axum is the straightforward
alternative. Want maximum ergonomics? You're in the right place.

Still fuzzy on why a framework calls *your* code instead of the other way around?
[What a framework even is](/guides/what-a-framework-even-is) is worth a detour - Rocket is a
textbook case: *"don't call us, we'll call you."*

## The mental model

Lock in the one sentence that makes every Rocket example readable:

> **The attribute is the route. The function signature is the request. The return type is the
> response.** The macros wire those three together.

When you see a handler, read it in three glances: the attribute tells you *which requests land
here*, the parameters tell you *what gets pulled out of the request*, and the return type tells
you *what gets sent back*. This phase leans on the attribute and return type; the signature
(path params, query, body, and *request guards*) gets its own treatment in Phases 2 and 3.

## Your first server

Start a project and add Rocket as a dependency:

```bash
cargo new books-api
cd books-api
cargo add rocket
```

*What just happened:* `cargo new` scaffolded a normal Rust binary crate, and `cargo add rocket`
wrote Rocket into `Cargo.toml` and fetched it. Rocket 0.5 is the current stable release, fully
async under the hood - but the macros handle the async setup for you, so you won't have to
think about that yet.

Now the smallest server that does something. Replace the contents of `src/main.rs`:

```rust
#[macro_use] extern crate rocket;

#[get("/")]
fn index() -> &'static str {
    "Hello, Rocket"
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![index])
}
```

*What just happened:* read it through the three-part model.

- `#[macro_use] extern crate rocket;` pulls Rocket's macros (`get`, `routes`, `launch`, and
  friends) into scope so you can use them bare. (`use rocket::{get, routes, launch};` works too
  and skips the `extern crate` line - same result. The `#[macro_use]` form is what Rocket's own
  docs lead with.)
- `#[get("/")]` is **the route**: "when a `GET` request arrives for the path `/`, run the
  function below." The attribute *is* the routing - there's no separate router line that
  mentions `index`.
- `fn index() -> &'static str { ... }` is the handler. It takes no parameters (nothing needed
  from the request yet), and its **return type is the response**: Rocket turns a `&'static str`
  into a proper HTTP response with the right `Content-Type`, so you return a plain string and
  Rocket does the wrapping.
- `rocket::build()` creates the application - an empty Rocket instance you then configure.
- `.mount("/", routes![index])` attaches your handlers. `routes![index]` collects the listed
  functions into a list Rocket understands, and `mount` hangs them off a base path (`/` here, so
  `index` answers at `/`). A hundred handlers is the same call with a longer `routes![...]`.
- `#[launch]` sits on a function that *returns the built Rocket*, turning it into the program's
  entry point. Note the return type is a bare `-> _` - the macro fills in the real (long, async)
  type for you. That underscore isn't a typo.

Now run it:

```bash
cargo run
```

```console
$ cargo run
🚀 Rocket has launched from http://127.0.0.1:8000
```

*What just happened:* `cargo run` compiled and started your server. On launch, Rocket prints its
configuration and the address it bound to - by default **port 8000** on localhost. Hit it from
another terminal:

```bash
curl localhost:8000
```

```console
$ curl localhost:8000
Hello, Rocket
```

*What just happened:* the request for `/` matched your `#[get("/")]` route, Rocket called
`index()`, took the `&'static str` it returned, and sent it back as the response body. A
working Rust web server in a handful of lines - none of them a hand-assembled router.

## `#[launch]` vs. writing `main` yourself

`#[launch]` is a convenience. Rocket is async, so the "real" entry point is an async `main`
running on Rocket's runtime - you *can* write it out by hand:

```rust
#[rocket::main]
async fn main() -> Result<(), rocket::Error> {
    rocket::build()
        .mount("/", routes![index])
        .launch()
        .await?;
    Ok(())
}
```

*What just happened:* `#[rocket::main]` sets up the async runtime so you can write an
`async fn main`, and you explicitly `.launch().await?` the built app yourself - exactly what
`#[launch]` generates under the hood.

💡 Use `#[launch]` unless you need to run code *before* the server starts (opening a database
connection, reading a startup file with `?` error handling). It's the right default here - it
keeps the entry point to a single expression.

## Our running example: a books API

Throughout this guide we grow one small service: a **books API**. The thing we're serving is a
book:

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

*What just happened:* nothing yet - just a plain Rust struct. But it's the spine of every phase
ahead: route to individual books by `id` (Phase 2), accept new ones as JSON bodies (Phase 3),
return them as JSON (Phase 4), store them in shared state (Phase 5), and wrap it all in a real
CRUD API with error handling (Phase 6). Next up: the part we skipped - the signature - starting
with paths that carry data, like `/books/<id>`.

## Recap

1. **Rocket is the ergonomic Rust web framework:** you write a function, put a route attribute
   like `#[get("/")]` above it, and that function becomes the handler. The code reads close to
   Flask or Express.
2. The trade vs. siblings: **axum** and **actix-web** favor explicit *builders*
   (`Router::new().route(...)`), while **Rocket** is *attribute-first* - concise and scannable,
   at the cost of macro "magic" this guide makes legible.
3. The mental model that unlocks everything: **the attribute is the route, the function signature
   is the request, and the return type is the response** - macros wire them together.
4. A first server is tiny: `#[get("/")]` over a handler, `rocket::build().mount("/", routes![index])`,
   and `#[launch]` as the entry point. Run with `cargo run`; Rocket prints its config and binds
   to **port 8000** by default.
5. **`#[launch]`** is shorthand for a hand-written `#[rocket::main] async fn main` that builds and
   `.launch().await`s the app. Use `#[launch]` unless you need setup code before the server starts.
6. The running example is a **books API** built around a `Book { id, title, author }` struct,
   which we'll grow across the guide.

## Quick check

Three questions on the ideas that have to stick - what makes Rocket *Rocket*, what the core
macros do, and how the entry point works:

```quiz
[
  {
    "q": "What is Rocket's defining design choice compared to axum and actix-web?",
    "choices": [
      "It defines routes with attribute macros placed above handler functions, rather than assembling a router with explicit builder calls",
      "It is the only Rust web framework that is synchronous instead of async",
      "It ships a built-in ORM and admin panel like a batteries-included framework",
      "It compiles to JavaScript so it can run in the browser"
    ],
    "answer": 0,
    "explain": "Rocket is attribute-first: #[get(\"/\")] above a function declares the route. axum and actix-web favor explicit builders like Router::new().route(...). The trade is ergonomic, concise code at the cost of macro magic."
  },
  {
    "q": "In the minimal server, what does `.mount(\"/\", routes![index])` do?",
    "choices": [
      "Attaches the handlers listed in routes![...] to the application at the base path \"/\"",
      "Starts the web server and blocks waiting for requests",
      "Declares that index() responds to POST requests instead of GET",
      "Reads the Rocket.toml config file and applies it"
    ],
    "answer": 0,
    "explain": "routes![index] is a macro that collects the listed handler functions, and .mount(\"/\", ...) hangs them off a base path on the built Rocket instance. The route attribute (#[get]) is what sets the method and path; #[launch] is what starts the server."
  },
  {
    "q": "What does the `#[launch]` attribute do?",
    "choices": [
      "It turns a function that returns the built Rocket into the program's entry point and runs the app - shorthand for a manual #[rocket::main] async fn main that builds then .launch().await's it",
      "It launches a separate browser window to preview the API",
      "It marks a handler as the default route when no other path matches",
      "It is required on every handler function, not just the entry point"
    ],
    "answer": 0,
    "explain": "#[launch] sits on a function returning the built Rocket (note the bare -> _, which the macro fills in). It generates the async entry point that builds and launches the app, so you don't write #[rocket::main] async fn main yourself."
  }
]
```


---

# Routing & Dynamic Paths

Phase 1 stood up a server that answered one fixed URL. Real APIs aren't fixed - `/books/42` and `/books/99` go to the same handler, with `42` and `99` riding along as data. This phase is about that wiring: how a URL turns into a function call with the right arguments already filled in.

**The path string declares the shape of the URL, and the function parameters receive the pieces.** Write `#[get("/books/<id>")]` and `<id>` is a hole in the path; the function needs a parameter literally named `id` to catch what lands there. Two rules fall out: the **names must line up** (matched by name, not position), and the **type does the parsing** - Rocket takes the raw text from the URL and tries to turn it into whatever type you declared, via a trait called `FromParam`.

> 📝 We're building a **books API** all the way through this guide. The data shape is a simple `Book { id, title, author }`. This phase focuses purely on *routing* - getting the right values into the right handlers. Real JSON comes in [Phase 4](04-responders.md); for now handlers return plain strings so the wiring stays visible.

## Method attributes: the verb and the path together

A Rocket route attribute carries two things at once - the HTTP method and the URL path. The method is the attribute *name*; the path is its argument.

```rust
#[get("/books")]
fn list() -> &'static str {
    "all books"
}

#[post("/books")]
fn create() -> &'static str {
    "created a book"
}

#[get("/books/<id>")]
fn show(id: u32) -> String {
    format!("book #{id}")
}

#[put("/books/<id>")]
fn update(id: u32) -> String {
    format!("updated book #{id}")
}

#[delete("/books/<id>")]
fn delete(id: u32) -> String {
    format!("deleted book #{id}")
}
```

*What just happened:* Five handlers, five HTTP verbs. `/books` appears twice - once for `#[get]` (list them), once for `#[post]` (create one) - and that's not a conflict: Rocket keys on **method + path together**, so `GET /books` and `POST /books` are entirely separate routes. The three `/books/<id>` routes likewise differ by verb. This is the REST shape you'll grow over the rest of the guide.

## Dynamic path segments: `<id>` to `id: u32`

`#[get("/books/<id>")] fn show(id: u32)`: the `<id>` says "this segment is a variable, call it `id`," and the function declares `id: u32`. When a request for `/books/42` arrives, Rocket pulls the text `"42"` out of that segment and asks: *can this become a `u32`?* It can, so `show` runs with `id = 42`.

That question is the whole game. The parameter type must implement **`FromParam`** - the trait that knows how to build a value from one URL segment. `u32`, `i64`, `String`, `bool`, and many more already implement it. `String` accepts anything, while `u32` only accepts digits that fit.

```rust
// /books/42   -> id = 42, show() runs
// /books/9999 -> id = 9999, show() runs
// /books/abc  -> "abc" is not a u32, so this route does NOT match
```

*What just happened:* That last line is the part people trip on. When `FromParam` parsing **fails**, Rocket doesn't error out - it decides this route **doesn't match** and moves on to try the next route. If nothing else matches `/books/abc`, the request ends in a 404. Your `id: u32` does double duty: it extracts the number *and* quietly rejects non-numbers, before a single line of your handler body runs.

> 💡 This is a feature, not a quirk. Parsing happens at the routing layer, so your handler body only ever sees a valid `u32` - no defensive `if let Ok(n) = id.parse()`. Let the signature be your validation.

### More than one segment, and "catch the rest"

Nothing stops you from having several dynamic segments. Match them all by name:

```rust
#[get("/order/<a>/<b>")]
fn order(a: u32, b: String) -> String {
    format!("a={a}, b={b}")
}
```

*What just happened:* `/order/7/express` gives `a = 7` (parsed as a number) and `b = "express"` (any text). Each `<...>` lines up with the parameter of the same name; mixing types per segment is fine.

To swallow *everything* after a point - serving files under a folder, say - use the trailing **multi-segment** form `<name..>`, which collects the rest of the path into a `PathBuf`:

```rust
use std::path::PathBuf;

#[get("/files/<path..>")]
fn files(path: PathBuf) -> String {
    format!("you asked for {}", path.display())
}
```

*What just happened:* `/files/covers/2024/rust.png` puts `covers/2024/rust.png` into `path` as a single `PathBuf`. The `..` makes it greedy across multiple segments; without it, `<path>` only matches one segment.

> ⚠️ `<path..>` hands you a `PathBuf` built from untrusted URL input. If you use it to read real files, guard against `../` directory-traversal tricks - Rocket has helpers for serving static files safely, pointed at in [Phase 8](08-where-to-go-next.md). Treat the segment as raw user input.

## Query params: the part after the `?`

Path segments are the URL's skeleton. The **query string** - everything after `?` - is for optional extras: pagination, search terms, filters. Declare query params in the attribute with `?<name>` and receive them as parameters, just like path segments. The difference is they're usually **optional**, expressed with `Option<T>`.

```rust
#[get("/books?<page>&<q>")]
fn list_books(page: Option<u32>, q: Option<String>) -> String {
    let page = page.unwrap_or(1);
    match q {
        Some(term) => format!("page {page}, searching for {term}"),
        None => format!("page {page}, no search"),
    }
}
```

*What just happened:* The path is still `/books`, but the attribute also declares two query params, joined with `&` exactly like a real URL. `GET /books` gives both as `None`. `GET /books?page=3` gives `page = Some(3)`, `q = None`. `GET /books?q=rust&page=2` fills both. Because they're `Option<T>`, a missing param isn't an error - it's `None`, and you decide the default (here, page 1).

> 💡 A plain `u32` instead of `Option<u32>` makes a query param **required** - a request missing it won't match the route. Use plain types only for params you truly always need.

When you have a *bunch* of related query params, listing them one by one gets noisy. Group them into a struct that derives **`FromForm`**, then receive the whole struct with the trailing `<params..>` form:

```rust
use rocket::form::FromForm;

#[derive(FromForm)]
struct Filter {
    page: Option<u32>,
    author: Option<String>,
}

#[get("/books?<params..>")]
fn filtered(params: Filter) -> String {
    format!("page={:?}, author={:?}", params.page, params.author)
}
```

*What just happened:* Same query string (`?page=2&author=hopper`), but collected into one tidy `Filter` value instead of a long parameter list. `FromForm` maps query (and HTML form) fields onto struct fields by name - you'll meet it again for POST bodies in [Phase 3](03-guards-and-data.md), same machinery.

## The trap: a route that exists but never runs

The single most common "why is my endpoint 404-ing?" in Rocket has nothing to do with the path. Writing `#[get(...)] fn whatever()` does **not** put the route into your application - it only *defines* it. You still have to register every handler in `routes![...]` and `mount` it onto Rocket.

```rust
#[launch]
fn rocket() -> _ {
    rocket::build().mount(
        "/",
        routes![list, create, show, update, delete, files, list_books],
    )
}
```

*What just happened:* Every handler we wrote is named in `routes![...]`. Add a new `#[get]` and forget to list it here, and Rocket compiles cleanly and starts fine - but that URL just 404s, with no error pointing at the cause.

> ⚠️ A handler missing from `routes!` fails **silently**. No compile error, no warning, no log line. When an endpoint mysteriously 404s, check `routes![...]` *first*, before touching the path string. This catches more people than any actual routing bug.

### When two routes could both match: ranking

Sometimes more than one route fits a URL, and Rocket chooses by **ranking**: more specific routes (static segments like `/books/new`) outrank less specific ones (dynamic segments like `/books/<id>`). A request to `/books/new` prefers the literal `new` route over the `<id>` catch-all - almost always what you want.

```rust
#[get("/books/new")]      // static -> ranked ahead
fn new_form() -> &'static str { "new book form" }

#[get("/books/<id>")]     // dynamic -> ranked behind
fn show(id: u32) -> String { format!("book #{id}") }
```

*What just happened:* `/books/new` hits `new_form` (the literal match wins); `/books/42` falls through to `show`. Rocket worked this ordering out for you. To override it, add `rank = N` to the attribute (lower numbers tried first) - reach for that only when the defaults genuinely don't fit.

## Recap

- A route attribute names both the **method and the path**: `#[get("/books")]`, `#[post("/books")]`, `#[put("/books/<id>")]`. Same path, different verb = different route.
- A dynamic segment `<id>` pairs with a same-named function parameter; the type implements **`FromParam`**, which parses the text and, on failure, makes the route **not match** (eventually a 404) instead of erroring.
- Use multiple `<...>` segments by name; use the trailing `<path..>` to capture the rest of the URL into a `PathBuf` (treat it as untrusted input).
- Query params go in the attribute as `?<page>&<q>` and arrive as parameters - `Option<T>` for optional, a plain type for required, or a `#[derive(FromForm)]` struct via `<params..>` to group them.
- Every handler must be listed in `routes![...]` and `mount`ed, or it **silently** won't serve. Overlapping routes are resolved by ranking (specific beats dynamic), overridable with `rank`.

## Quick check

```quiz
[
  {
    "q": "A request hits /books/abc but your only matching route is #[get(\"/books/<id>\")] fn show(id: u32). What happens?",
    "choices": ["show() runs with id set to 0", "Rocket panics at runtime", "The route doesn't match (abc isn't a u32), so it falls through - likely a 404", "It compiles but returns a 500 error"],
    "answer": 2,
    "explain": "FromParam parsing of \"abc\" into u32 fails, so the route does not match. Rocket tries other routes and, finding none, returns 404. Your handler body never runs."
  },
  {
    "q": "You added #[get(\"/health\")] fn health() but it 404s in the running app. The path is correct. What's the most likely cause?",
    "choices": ["You forgot to add `health` to routes![...] and mount it", "You need a semicolon after the attribute", "Rocket requires health checks to use #[post]", "The function name must match the path"],
    "answer": 0,
    "explain": "Defining a handler doesn't register it. It must be named in routes![...] and mounted. This omission fails silently - no compile error - so it's the first thing to check on a mystery 404."
  },
  {
    "q": "Which signature makes a `page` query param optional, defaulting to your own value when it's missing?",
    "choices": ["fn list(page: u32)", "fn list(page: Option<u32>)", "fn list(page: PathBuf)", "fn list(page: bool)"],
    "answer": 1,
    "explain": "Option<u32> means the param may be absent (None) without breaking the match, letting you supply a default. A plain u32 would make the param required, so a request without it wouldn't match the route."
  }
]
```


---

# Request Guards & Data

The mental model that makes the rest of Rocket click: look at any handler's parameter list and
ask of each parameter one question: **is this the request body, or is this a guard?**

- **Data** is the request body - the JSON or form a client `POST`s. There is exactly **one** data
  parameter per route, and you mark it with the `data = "<name>"` attribute.
- **A request guard** is everything else. Any non-data parameter whose type knows how to build itself
  from the incoming request - a path segment, a query value, an authenticated user, a database
  connection. Each guard must **succeed** before the handler body runs. If one fails, Rocket never
  calls your function; it forwards to another route or returns an error like `401`.

That second bullet is Rocket's signature trick. Authentication, authorization, "is this user logged
in" - they all become a **type in your signature**. No `if not authenticated: return 401` at the
top of every handler. Add a parameter of type `User`, and Rocket guarantees the body only runs
once that `User` exists. We build exactly that below.

> 📝 You met one guard already without us naming it: in Phase 2, `id: usize` in
> `#[get("/books/<id>")]` was a guard backed by `FromParam`. Same machinery - a type that must
> succeed before the handler runs.

## Reading the body: `Json<T>`

Most REST work is "client sends JSON, we deserialize it." Rocket does this with `Json<T>`, where `T`
derives `Deserialize`. Json needs a feature flag, so enable it first:

```
cargo add rocket --features json
```

Now a `POST` that accepts a new book. Recall our running types - a stored `Book` has an `id`, but the
client creating one does not know the id yet, so they send a `NewBook`:

```rust
use rocket::serde::{Deserialize, Serialize};
use rocket::serde::json::Json;

#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct NewBook {
    title: String,
    author: String,
}

#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct Book {
    id: usize,
    title: String,
    author: String,
}

#[post("/books", data = "<book>")]
fn create(book: Json<NewBook>) -> Json<Book> {
    let new = book.into_inner();           // unwrap Json<NewBook> -> NewBook
    let stored = Book { id: 1, title: new.title, author: new.author };
    Json(stored)
}
```

*What just happened:* The attribute `data = "<book>"` tells Rocket "the parameter named `book` is the
request body." Rocket reads the body, deserializes the JSON into `NewBook`, and hands you
`Json<NewBook>`. `.into_inner()` peels off the `Json` wrapper to get the plain struct. Returning
`Json<Book>` serializes back to JSON on the way out (more on responders in Phase 4). The
`#[serde(crate = "rocket::serde")]` line is bookkeeping - it points the derive at Rocket's re-exported
serde so you do not need serde as a separate dependency.

> ⚠️ **One data parameter per route.** A route can have many guards but only a single `data`
> parameter. A handler cannot read two bodies - there is only one request body to read.

## Reading a form: `Form<T>`

HTML forms send `application/x-www-form-urlencoded`, not JSON. Same idea, different wrapper: use
`Form<T>` with a type that derives `FromForm`.

```rust
use rocket::form::Form;

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

#[post("/books", data = "<form>")]
fn create_from_form(form: Form<NewBook>) -> String {
    let book = form.into_inner();
    format!("Got: {} by {}", book.title, book.author)
}
```

*What just happened:* Structurally this is identical to the Json version - `data = "<form>"` marks the
body, `Form<NewBook>` parses it, `.into_inner()` unwraps. The only differences are the wrapper type
(`Form` instead of `Json`) and the derive (`FromForm` instead of `Deserialize`). `FromForm` also
supports validation attributes - you can write `#[field(validate = len(1..))]` to reject an empty
title before your handler ever sees it.

## Request guards: auth as a type

A request guard is any type that implements **`FromRequest`** and appears as a non-data
parameter. Rocket runs each guard's `from_request` against the incoming request; if it returns
`Success`, the handler runs with that value; if it returns `Error` or `Forward`, the handler is
skipped.

A tiny API-key guard: `/admin` should run only when the request carries the header `x-api-key`.

```rust
use rocket::request::{self, FromRequest, Request};
use rocket::http::Status;

struct ApiKey(String);

#[rocket::async_trait]
impl<'r> FromRequest<'r> for ApiKey {
    type Error = ();

    async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
        match req.headers().get_one("x-api-key") {
            Some(k) => request::Outcome::Success(ApiKey(k.to_string())),
            None => request::Outcome::Error((Status::Unauthorized, ())),
        }
    }
}

#[get("/admin")]
fn admin(_key: ApiKey) -> &'static str {
    "secret"  // this line only runs if the guard succeeded
}
```

*What just happened:* `ApiKey` taught Rocket how to build itself from a request - read the header, and
return `Success` with the key, or `Error((Status::Unauthorized, ()))` when it is missing. Because
`admin` takes an `ApiKey` parameter, Rocket calls `from_request` **before** the body. No header? The
handler never executes and the client gets `401 Unauthorized`. The `_key` underscore just says "I
need the guard to pass but I am not using the value here."

> 💡 This is the elegant payoff. The presence of `ApiKey` in the signature is the entire auth check.
> Swap it for a real `User` guard that validates a session cookie and looks the user up, and every
> handler that takes `user: User` is automatically logged-in-only - no boilerplate `if` at the top of
> each function, and impossible to forget, because forgetting means deleting a parameter you need.

## You don't write every guard yourself

Many guards ship with Rocket. You will meet `&State<T>` in Phase 5 - it is a guard that hands your
handler shared application state (a database pool, a config). Cookies, the client's `IpAddr`, content
types, and more are all guards too. The pattern is uniform: if a type can be derived from the request,
it can sit in your signature and be checked before your code runs.

> ⚠️ **Guard order is parameter order.** Rocket runs guards left to right. If one guard depends on
> another having already succeeded (say, a `User` guard that assumes a `Db` connection guard ran),
> list the dependency first. And remember the one rule from earlier: the single `data` parameter can
> sit anywhere in the list, but there is only ever one.

## Recap

- A handler parameter is **either the body** (one `data = "<x>"` parameter) **or a request guard**
  (every other parameter - a type that must succeed first).
- **`Json<T>`** (with `#[derive(Deserialize)]` and `cargo add rocket --features json`) reads a JSON
  body; **`Form<T>`** (with `#[derive(FromForm)]`) reads an HTML form. Unwrap either with
  `.into_inner()`.
- A **request guard** is any type implementing **`FromRequest`**; if `from_request` returns `Error`
  or `Forward`, the handler never runs (e.g. a missing API key yields `401`).
- Guards make **auth a type in the signature** - add a `User` parameter and the handler is
  logged-in-only, checked automatically, impossible to forget.
- Built-in guards exist too (`&State<T>`, cookies, and more); **guard order follows parameter order**,
  and there is **only one data parameter** per route.

## Quick check

Make sure the core idea stuck before moving on:

```quiz
[
  {
    "q": "How many `data` parameters can a single Rocket route have?",
    "choices": ["As many as you declare", "Exactly one", "One per guard", "Zero - bodies use guards"],
    "answer": 1,
    "explain": "There is only one request body, so a route has at most one data parameter. Everything else in the signature is a request guard."
  },
  {
    "q": "A handler takes an ApiKey guard whose from_request returns an Unauthorized error when the header is missing. A request arrives without the header. What happens?",
    "choices": ["The handler runs with an empty ApiKey", "The handler body runs, then returns 401", "The handler never runs; the client gets 401", "Rocket panics"],
    "answer": 2,
    "explain": "Guards run before the handler body. A failing guard means the handler is never called - Rocket returns the guard's error (401 here)."
  },
  {
    "q": "Which trait must a type implement to be usable as a request guard?",
    "choices": ["Deserialize", "FromForm", "FromRequest", "Responder"],
    "answer": 2,
    "explain": "FromRequest defines how a type builds itself from the incoming request. Deserialize and FromForm are for body data; Responder is for return types."
  }
]
```


---

# Responders

**In Rocket, the return type of your handler *is* the response.** You don't reach for a `Response` object and start setting status codes and headers by hand. Pick a Rust type that knows how to turn itself into HTTP, return a value of that type, and Rocket does the rest.

The trait that makes this work is **`Responder`**. Any type that implements it can be a handler's return type, and Rocket already implements it for the types you reach for most: `String`, `Json<T>`, `Option<T>`, `Result<T, E>`, tuples like `(Status, T)`, and a handful of `status::*` helpers.

Coming from frameworks where you write `res.status(404).json(...)`, you might expect to *do* something to produce a response. In Rocket you *describe* it with a type. Want a 404 when a book isn't found? Return `Option<Json<Book>>` - `None` becomes a 404 with zero extra code. That's the whole design.

> 📝 We're still growing the same **books API**. Our model is the same as before:
>
> ```rust
> use rocket::serde::Serialize;
>
> #[derive(Serialize)]
> #[serde(crate = "rocket::serde")]
> struct Book {
>     id: u32,
>     title: String,
>     author: String,
> }
> ```
>
> `Serialize` is what lets `Json<Book>` write itself out as JSON.

## The built-in responders, in one tour

Each of these is just a return type. Rocket sees it and produces the matching HTTP response.

```rust
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::response::status;

// Plain text - &str and String are responders (200 OK, text/plain).
#[get("/ping")]
fn ping() -> &'static str {
    "pong"
}

// JSON - Json<T> where T: Serialize (200 OK, application/json).
#[get("/books/first")]
fn first() -> Json<Book> {
    Json(Book { id: 1, title: "Dune".into(), author: "Herbert".into() })
}

// (Status, T) - same body, but you choose the status line.
#[get("/teapot")]
fn teapot() -> (Status, &'static str) {
    (Status::ImATeapot, "no coffee here")
}
```

*What just happened:* three handlers, three different responses, and not one of them touches a response builder. `&'static str` produces a 200 with `text/plain`. `Json(book)` serializes the struct and sets `application/json`. The tuple `(Status, T)` keeps `T`'s body but swaps the status - here a 418. The type carried all the information Rocket needed.

The full cast of built-in responders you'll lean on:

- **`&str` / `String`** - plain text, 200 OK.
- **`Json<T>`** (where `T: Serialize`, from `rocket::serde::json::Json`) - JSON body, 200 OK.
- **`Option<T>`** - `Some(x)` becomes `x`'s response; **`None` becomes a 404**.
- **`Result<T, E>`** - `Ok(x)` becomes `x`'s response; `Err(e)` becomes `e`'s response (if `E: Responder`).
- **`(Status, T)`** - `T`'s body with the status you name.
- **`status::Created`, `status::NoContent`, `status::Custom`, `status::NotFound`** - small wrappers from `rocket::response::status` for common HTTP semantics.

## "Found, or 404" - the most idiomatic Rocket you'll write

Almost every read-by-id endpoint has the same shape: look it up, return it if it exists, 404 if it doesn't. In most frameworks that's an `if` and an early return. In Rocket it's a return type.

```rust
// Pretend this is your data layer.
fn store_get(id: u32) -> Option<Book> {
    // ... look up by id, return Some(book) or None ...
    # None
}

#[get("/books/<id>")]
fn show(id: u32) -> Option<Json<Book>> {
    store_get(id).map(Json)
}
```

*What just happened:* `store_get` already returns `Option<Book>`, so `.map(Json)` turns it into `Option<Json<Book>>` - wrapping the inner `Book` in `Json` only when it's `Some`. Rocket then reads the `Option`: a `Some(Json(book))` serializes to JSON with 200, and a `None` becomes a clean 404 automatically. The "not found" path is handled entirely by the type.

> 💡 `Option<Json<T>>` is the single most Rocket-idiomatic way to express "found or 404." When you catch yourself writing an explicit 404 branch for a lookup, reach for this instead - the framework already speaks it.

## Setting a status on purpose - the create case

Reads are usually 200. Writes often aren't: creating a resource should answer **201 Created**, and a successful delete with no body is **204 No Content**. You have two clean ways to say so.

The plain tuple is the most direct:

```rust
#[post("/books", data = "<book>")]
fn create(book: Json<Book>) -> (Status, Json<Book>) {
    // ... save book.into_inner() somewhere ...
    (Status::Created, book)
}
```

*What just happened:* the tuple `(Status::Created, Json<Book>)` says "201, with this JSON body." `Status::Created` is `rocket::http::Status::Created` (201). The body is the same `Json<Book>` you'd return on a 200 - only the status line changed. Rocket reads the tuple left-to-right: status first, responder second.

When you also want to advertise *where* the new resource lives, `status::Created` carries a `Location` header for you:

```rust
use rocket::response::status;

#[post("/books", data = "<book>")]
fn create_located(book: Json<Book>) -> status::Created<Json<Book>> {
    status::Created::new("/books/1").body(book)
}
```

*What just happened:* `status::Created::new("/books/1")` builds a 201 response and sets the `Location: /books/1` header to point at the freshly created book; `.body(book)` attaches the JSON. Callers that follow `Location` (and plenty of clients do) land directly on the new resource. Same 201 as the tuple, plus the header - pick this when the location matters.

## The other `status::*` helpers, and rolling your own

Two more helpers cover the common cases:

- **`status::NoContent`** - a 204 with no body, the right answer for a successful `DELETE` or an update that returns nothing.
- **`status::Custom(Status, T)`** - any status you want paired with any responder body, when none of the named helpers fit. Think of it as the tuple's more explicit sibling.

When your own type needs a specific HTTP shape, you don't have to hand-assemble a response - you can **derive** `Responder`:

```rust
use rocket::serde::json::Json;

#[derive(rocket::response::Responder)]
#[response(status = 201, content_type = "json")]
struct NewBook(Json<Book>);
```

*What just happened:* `#[derive(Responder)]` reads the `#[response(...)]` attribute and teaches `NewBook` to render as a 201 with a JSON content type, using the wrapped `Json<Book>` as the body. Now any handler can return `NewBook` and get that exact response - the HTTP semantics live with the type instead of being repeated at every return site. Reach for this once a particular response shape shows up in more than one handler.

## A teaser: `Result` for clean errors

Because `Result<T, E>` is a responder whenever both `T` and `E` are, you can already express success-or-error in a signature:

```rust
#[get("/books/<id>")]
fn show_or_status(id: u32) -> Result<Json<Book>, Status> {
    store_get(id)
        .map(Json)
        .ok_or(Status::NotFound)
}
```

*What just happened:* `Ok(Json(book))` becomes a 200 JSON response; `Err(Status::NotFound)` becomes a 404, because `Status` is itself a responder. This works today - but returning a bare `Status` on every error gets repetitive, and the error bodies are empty. In Phase 6 we'll pair `Result` with **error catchers** (`#[catch(404)]`) so a single place defines what a 404 (or 500) actually looks like across the whole API. For now, just notice that the door is open: errors are responses too.

## Recap

- **The return type *is* the response.** Pick a type that implements `Responder`; Rocket turns it into HTTP. You describe the response, you don't build it.
- Built-in responders cover the essentials: `&str`/`String` (text), `Json<T>` (JSON), `Option<T>`, `Result<T, E>`, `(Status, T)`, and the `status::*` helpers.
- **`Option<Json<T>>` is the idiomatic "found or 404"** - `None` becomes a 404 with no extra code.
- Set a status with `(Status::Created, Json(book))`, or use `status::Created::new(...).body(...)` to also send a `Location` header; `status::NoContent` is your 204.
- `#[derive(Responder)]` lets your own type own its HTTP shape (status + content type) once, instead of repeating it at every handler.
- `Result<T, E>` already expresses success/error as a response - the foundation for clean error handling with catchers in Phase 6.

## Quick check

Make sure the core ideas stuck:

```quiz
[
  {
    "q": "A handler returns Option<Json<Book>> and the value is None. What does Rocket send?",
    "choices": ["A 200 with an empty body", "A 404 Not Found", "A 500 Internal Server Error", "A compile error - Option isn't a responder"],
    "answer": 1,
    "explain": "Option<T> is a responder: Some(x) yields x's response, and None automatically becomes a 404. That's why Option<Json<T>> is the idiomatic 'found or 404' pattern."
  },
  {
    "q": "You want a create endpoint to return 201 Created with a JSON body. Which return value works?",
    "choices": ["Json(book) - it defaults to 201 for POST", "(Status::Created, Json(book))", "Status::Created alone", "Created(book) with no import"],
    "answer": 1,
    "explain": "The tuple (Status, T) keeps T's body but sets the status you name. (Status::Created, Json(book)) gives a 201 with the JSON body. status::Created::new(...).body(...) is the alternative that also adds a Location header."
  },
  {
    "q": "Why does returning Result<Json<Book>, Status> compile and work as a handler?",
    "choices": ["Rocket special-cases Result in the routing macro", "Result is a responder when both the Ok and Err types are responders - and Status is one", "Status implements Serialize", "It only works inside an async handler"],
    "answer": 1,
    "explain": "Result<T, E> implements Responder when both T and E do. Json<Book> is a responder and Status is a responder, so Ok yields the JSON (200) and Err(Status::NotFound) yields a 404."
  }
]
```


---

# Managed State & Fairings

So far every handler in the books API has been an island. Phase 3 taught request guards (per-route inputs that can reject), Phase 4 taught responders (the shape of what you send back). A real service needs two more things: a place to keep **shared stuff** every handler touches - the book store, a database pool, a config value - and a way to run logic **for every request**, like logging or attaching a header. Rocket has one tool for each.

**A shared dependency is *managed state*: register it once on the builder with `.manage(value)` and pull it into any handler through the `&State<T>` guard. A cross-cutting lifecycle behavior is a *fairing*: attach it once with `.attach(...)` and it runs on every request and/or response.** State is "give me the thing." Fairings are "do this thing, always."

> 📝 This is the 🔴 advanced part of the guide because both features lean on Rust concepts you now need at the same time: shared references and interior mutability for state, and the async trait dance for fairings. Take it slowly; the payoff is that the books API stops being a toy.

## Managed state: one source of truth

Until now our handlers had nowhere to keep the books between requests. Define a struct that holds the data, hand it to Rocket with `.manage(...)`, and any handler can ask for it by adding a `&State<AppState>` parameter.

```rust
use rocket::serde::json::Json;
use rocket::State;
use std::collections::HashMap;
use std::sync::Mutex;

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

struct AppState {
    books: Mutex<HashMap<u32, Book>>,
}

#[get("/books")]
fn list(state: &State<AppState>) -> Json<Vec<String>> {
    let books = state.books.lock().unwrap();
    let titles = books.values().map(|b| b.title.clone()).collect();
    Json(titles)
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .manage(AppState { books: Mutex::new(HashMap::new()) })
        .mount("/", routes![list])
}
```

*What just happened:* `.manage(AppState { ... })` stored one `AppState` value inside Rocket's state registry, keyed by its type. The `list` handler asked for it by writing `state: &State<AppState>` - a request guard, exactly like Phase 3's, except this one never fails as long as you registered the type. `&State<AppState>` derefs to `&AppState`, so `state.books` reaches the field directly. Every request to `/books` sees the *same* `AppState`, so anything one handler writes, the next one reads.

### The shared-reference catch

⚠️ Look closely at that signature: `&State<AppState>` is a **shared** (`&`) reference. Rocket may run handlers concurrently, so it can only ever hand you a read-only borrow of your state - there is no `&mut State`. That's why the `books` field is a `Mutex<HashMap<...>>` and not a bare `HashMap`. To *change* the data you need **interior mutability**: you take the lock, mutate through the guard, and the lock makes concurrent access safe.

```rust
#[post("/books", data = "<book>")]
fn add(book: Json<Book>, state: &State<AppState>) -> Json<Book> {
    let mut books = state.books.lock().unwrap();
    let stored = book.into_inner();
    books.insert(stored.id, stored.clone());
    Json(stored)
}
```

*What just happened:* even though `state` is a shared reference, `state.books.lock()` returns a `MutexGuard` we can treat as `&mut HashMap`, so `insert` mutates the real, shared map. The lock is held only for the body of this function and released when `books` drops. If two requests `POST` at once, one waits for the other's lock - no torn writes. For read-heavy workloads reach for `RwLock` instead of `Mutex`; the principle is the same.

> 💡 A `Mutex<HashMap>` is fine for a demo, not how you'd store real data. For a real database you manage a **connection pool** instead - typically [`rocket_db_pools`](/guides/rocket-from-zero), which sets up the pool as managed state and gives handlers a connection through the same `&State`-style guard. Same pattern; only the type inside changes.

### You can manage more than one type

State is keyed by type, so you can `.manage(...)` several different values and ask for whichever ones a handler needs:

```rust
struct Config {
    site_name: String,
}

#[get("/about")]
fn about(config: &State<Config>) -> String {
    format!("Welcome to {}", config.site_name)
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .manage(AppState { books: Mutex::new(HashMap::new()) })
        .manage(Config { site_name: "The Stacks".into() })
        .mount("/", routes![list, add, about])
}
```

*What just happened:* we registered two distinct types - `AppState` and `Config`. The `about` handler only needs the config, so it asks for just `&State<Config>`; `list` and `add` ask for `&State<AppState>`. Rocket matches each request by the type inside `State<...>`. ⚠️ The flip side of type-keying: if a handler asks for a type you never `.manage`d, the guard fails and Rocket rejects the request (a 500 in dev), with a log line naming the missing type. The fix is always "you forgot to `.manage` it."

## Fairings: middleware for the whole lifecycle

Managed state answers "what do my handlers share?" Fairings answer "what should happen on *every* request or response, regardless of which handler runs?" Logging each request, stamping a header onto every response, setting up a resource at startup, wiring CORS - none of that belongs in any single handler. **Fairings are Rocket's middleware**: callbacks that hook into the request/response lifecycle and run globally.

The full-power way implements the `Fairing` trait: an `info()` method (a name plus which lifecycle stages you want), then async callbacks for those stages - commonly `on_request` and `on_response`, with `on_ignite`/`on_liftoff` for startup work.

```rust
use rocket::{Request, Response, Data};
use rocket::fairing::{Fairing, Info, Kind};

struct RequestLogger;

#[rocket::async_trait]
impl Fairing for RequestLogger {
    fn info(&self) -> Info {
        Info { name: "Request logger", kind: Kind::Request | Kind::Response }
    }

    async fn on_request(&self, req: &mut Request<'_>, _data: &mut Data<'_>) {
        println!("--> {} {}", req.method(), req.uri());
    }

    async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
        println!("<-- {} for {}", res.status(), req.uri());
    }
}
```

*What just happened:* `info()` declares the fairing's name (shown in startup logs) and its `Kind` - here `Request | Kind::Response`, telling Rocket to call both callbacks. `on_request` fires before the matched handler runs and can read or tweak the incoming request; `on_response` fires after, with mutable access to the outgoing `Response`. `#[rocket::async_trait]` is what lets a trait have `async fn` methods. This single fairing now logs every request and response across the entire app - no handler had to opt in.

### Ad-hoc fairings for the quick ones

Writing a whole struct + trait impl is overkill for one tiny hook. Rocket gives you **ad-hoc fairings** via `AdHoc`: pass a name and a closure, get a fairing back.

```rust
use rocket::fairing::AdHoc;

#[launch]
fn rocket() -> _ {
    rocket::build()
        .manage(AppState { books: Mutex::new(HashMap::new()) })
        .mount("/", routes![list, add])
        .attach(RequestLogger)
        .attach(AdHoc::on_response("Server header", |_req, res| Box::pin(async move {
            res.set_raw_header("X-Server", "Rocket");
        })))
}
```

*What just happened:* `.attach(...)` registers a fairing - both `RequestLogger` and the ad-hoc one - and Rocket runs them in attach order. `AdHoc::on_response` takes a name and a closure; because the closure is async, its body is wrapped in `Box::pin(async move { ... })`. This one stamps an `X-Server: Rocket` header onto *every* response, in about four lines. CORS works the same way and is the textbook fairing use case - attach a configured `rocket_cors::Cors` fairing once and every response gains the right headers, no per-handler code.

## Guards or fairings? The deciding question

These two tools overlap enough to confuse, so here's the clean split.

> 💡 **Request guards are per-route, type-driven, and can reject.** They run only for the handler they're added to, and if one fails (bad token, missing state, malformed input) the request stops there. Use them for *inputs and access control*. **Fairings are global lifecycle hooks that run for every request/response and don't gate individual routes.** Use them for *cross-cutting concerns* - logging, response headers, CORS, startup initialization.

The litmus test: if the logic should be able to **block one specific route**, it's a guard (Phase 3). If it should **happen everywhere and just observe or decorate**, it's a fairing. Authentication that rejects unauthorized callers → guard. A header on every response → fairing. Reaching for a fairing to do per-route work means re-checking which route you're on inside a global hook - the signal you wanted a guard all along.

With shared state and lifecycle hooks in hand, the books API has a real spine. Next: full CRUD, and failing gracefully with error catchers.

## Recap

- **Managed state** is shared data registered once with `.manage(value)` and pulled into handlers via the `&State<T>` request guard. Every request sees the same value.
- `&State<T>` is a **shared** reference, so changing the data needs **interior mutability** - a `Mutex` (or `RwLock`) field. Take the lock, mutate, release.
- State is **keyed by type**: you can manage several distinct types, but asking for a type you never managed fails the request.
- For real databases you manage a **connection pool** (e.g. `rocket_db_pools`) rather than a `Mutex<HashMap>` - same pattern, production-grade type.
- **Fairings** are Rocket's middleware: implement the `Fairing` trait (`info()` + `on_request`/`on_response`, plus `on_ignite`/`on_liftoff`) or use **ad-hoc fairings**, and attach them with `.attach(...)`. They run globally.
- **Guards = per-route, can reject; fairings = global hooks (logging, headers, CORS, init).** Pick by whether the logic should block a single route.

## Quick check

```quiz
[
  {
    "q": "Why does writable managed state usually wrap its data in a Mutex?",
    "choices": ["Rocket requires every managed value to be a Mutex", "The &State<T> guard gives only a shared reference, so mutation needs interior mutability", "Mutex makes handlers run faster", "Without it the state would not be shared between requests"],
    "answer": 1,
    "explain": "Handlers receive &State<T>, a shared reference, and Rocket may run them concurrently. To mutate shared data safely you need interior mutability such as a Mutex or RwLock."
  },
  {
    "q": "A handler asks for &State<Config>, but you never called .manage(Config). What happens?",
    "choices": ["Rocket auto-creates a default Config", "It compiles but silently passes None", "The State guard fails and Rocket rejects the request", "The server refuses to start"],
    "answer": 2,
    "explain": "Managed state is keyed by type. Extracting a type you never managed makes the guard fail, so Rocket rejects that request (a 500 in dev) and logs the missing type."
  },
  {
    "q": "You want to add an X-Server header to every response your app sends. Which tool fits?",
    "choices": ["A request guard on each handler", "An ad-hoc fairing attached with .attach", "A new managed state value", "A custom responder per route"],
    "answer": 1,
    "explain": "A header on every response is a cross-cutting, global concern with no per-route gating - exactly what a fairing is for. AdHoc::on_response attached with .attach does it in a few lines."
  }
]
```


---

# A REST API with Error Catchers

This is the payoff phase. Everything you've built - attribute routes, dynamic paths, `Json` data, responders, managed state - comes together into a real REST resource, plus the one piece that's been missing: a single place to define what an *error* looks like across the API.

**A REST resource is five attribute-routed handlers over the managed store.** List, show one, create, update, delete - the whole vocabulary of CRUD over HTTP. Each handler pulls what it needs from the signature (`&State<AppState>` for the store, `id: u32` from the path, `Json<NewBook>` from the body), and each handler's **return type carries the per-request outcome** - an `Option` that 404s when a book is missing, a `(Status, Json<Book>)` that says "201 Created."

There's a second layer: **responders handle outcomes your handler produces; catchers handle failures the framework produces.** A typo'd URL that matches no route, a malformed JSON body that never reaches your function, a guard that rejects the request - your handler never runs for those, so it can't shape the response. That's a catcher's job.

> 📝 We're still growing the same **books API**. From Phase 5 we have the managed store, and the model from Phase 4 plus an input type for writes:
>
> ```rust
> use std::collections::HashMap;
> use std::sync::Mutex;
> use rocket::serde::{Serialize, Deserialize};
>
> #[derive(Serialize, Clone)]
> #[serde(crate = "rocket::serde")]
> struct Book {
>     id: u32,
>     title: String,
>     author: String,
> }
>
> // The shape clients send on create/update - no id, the server owns that.
> #[derive(Deserialize)]
> #[serde(crate = "rocket::serde")]
> struct NewBook {
>     title: String,
>     author: String,
> }
>
> struct AppState {
>     books: Mutex<HashMap<u32, Book>>,
> }
> ```
>
> `Book` gains `Clone` so we can hand a copy out of the lock; `NewBook` is `Deserialize` because it arrives as a JSON body.

## The five handlers

Here's the whole resource. Read it once top to bottom - notice how little ceremony there is per endpoint, and how each return type does the talking.

```rust
use rocket::State;
use rocket::http::Status;
use rocket::serde::json::Json;

// READ all - GET /books
#[get("/books")]
fn list(state: &State<AppState>) -> Json<Vec<Book>> {
    let books = state.books.lock().unwrap();
    Json(books.values().cloned().collect())
}

// READ one - GET /books/<id>
#[get("/books/<id>")]
fn show(id: u32, state: &State<AppState>) -> Option<Json<Book>> {
    let books = state.books.lock().unwrap();
    books.get(&id).cloned().map(Json)
}

// CREATE - POST /books
#[post("/books", data = "<new>")]
fn create(new: Json<NewBook>, state: &State<AppState>) -> (Status, Json<Book>) {
    let mut books = state.books.lock().unwrap();
    let id = books.keys().max().copied().unwrap_or(0) + 1;
    let book = Book { id, title: new.title.clone(), author: new.author.clone() };
    books.insert(id, book.clone());
    (Status::Created, Json(book))
}

// UPDATE - PUT /books/<id>
#[put("/books/<id>", data = "<upd>")]
fn update(id: u32, upd: Json<NewBook>, state: &State<AppState>) -> Option<Json<Book>> {
    let mut books = state.books.lock().unwrap();
    if !books.contains_key(&id) {
        return None;
    }
    let book = Book { id, title: upd.title.clone(), author: upd.author.clone() };
    books.insert(id, book.clone());
    Some(Json(book))
}

// DELETE - DELETE /books/<id>
#[delete("/books/<id>")]
fn delete(id: u32, state: &State<AppState>) -> Status {
    let mut books = state.books.lock().unwrap();
    if books.remove(&id).is_some() {
        Status::NoContent
    } else {
        Status::NotFound
    }
}
```

*What just happened:* five handlers, one per CRUD operation, all reading the shared store through `&State<AppState>` and all locking the `Mutex` before they touch the map.

- **`list`** clones every value out of the map and wraps the `Vec` in `Json` - a 200 with a JSON array. The clone leaves the lock as soon as the function returns.
- **`show`** is the Phase-4 idiom over real state: `.get(&id).cloned().map(Json)` is `Some(Json(book))` when it exists, `None` otherwise - and `None` becomes a free 404.
- **`create`** computes the next id, builds the `Book` from the `NewBook` body, inserts a clone, and returns `(Status::Created, Json(book))` - a 201 with the created record. The server assigns the id, not the client.
- **`update`** checks existence first and returns `None` (→ 404) for a missing book, otherwise replaces the record and returns it with a 200.
- **`delete`** returns the bare `Status` responder: `204 No Content` when something was removed, `404 Not Found` when there was nothing to remove.

Each "not found" here is a **responder-level outcome** - your handler ran, looked, and decided. The catchers below handle a different category of miss. Now mount them - same `routes!` you already know, just longer:

```rust
#[launch]
fn rocket() -> _ {
    rocket::build()
        .manage(AppState { books: Mutex::new(HashMap::new()) })
        .mount("/", routes![list, show, create, update, delete])
}
```

*What just happened:* `.manage(...)` registers the store so every `&State<AppState>` parameter resolves to it (Phase 5), and `routes![...]` lists all five handlers. That's a complete, working CRUD API. The next section adds the error layer.

## Error catchers: one place for what failure looks like

Try requesting `GET /bookz` (a typo). No route matches, so **none of your handlers run** - Rocket itself produces a 404, by default its generic HTML error page. For a JSON API, an HTML error in the middle of JSON responses is jarring and breaks clients that always parse the body as JSON.

This is what **catchers** are for. A catcher is a function annotated with `#[catch(<status>)]` that produces the response for a given error status when no responder did. Register catchers separately from routes, with `.register(...)`.

```rust
use rocket::serde::json::{json, Value};
use rocket::Request;

#[catch(404)]
fn not_found(req: &Request) -> Value {
    json!({ "error": "not found", "path": req.uri().path().to_string() })
}

#[catch(422)]
fn unprocessable(_req: &Request) -> Value {
    json!({ "error": "unprocessable entity", "hint": "check your JSON body and field types" })
}

#[catch(500)]
fn server_error(_req: &Request) -> Value {
    json!({ "error": "internal server error" })
}
```

*What just happened:* three catchers, one per status we care about. The `&Request` parameter gives a catcher access to the failed request - `not_found` reads `req.uri().path()` so the error body tells the client *which* path missed. The return type is `rocket::serde::json::Value`, and `json!({ ... })` builds it inline - how each catcher emits JSON instead of HTML. The status code is already decided by `#[catch(N)]`; the function only supplies the body.

> ⚠️ That **422** catcher is doing more than it looks. When a client `POST`s a body that isn't valid JSON, or is valid JSON but missing a field `NewBook` requires, the `Json<NewBook>` data guard *fails before your handler is ever called* - and Rocket signals that with **422 Unprocessable Entity**, not 400. Your `create` function never runs, so it can't shape that response. The 422 catcher is the only place you get to.

Now register them. Catchers go through `.register(base, catchers![...])`, parallel to how routes go through `.mount`:

```rust
#[launch]
fn rocket() -> _ {
    rocket::build()
        .manage(AppState { books: Mutex::new(HashMap::new()) })
        .mount("/", routes![list, show, create, update, delete])
        .register("/", catchers![not_found, unprocessable, server_error])
}
```

*What just happened:* `.register("/", catchers![...])` attaches the catchers at the `/` base, so they apply to the whole app - `catchers!` is the catcher counterpart to `routes!`. Now every framework-level 404, 422, and 500, whatever tripped it, comes back as your consistent JSON shape instead of HTML. Catchers can also be **scoped to a path** by registering them under a different base (e.g. `.register("/api", ...)`).

## Catchers vs. responder-level errors - use both

These two mechanisms are not competitors; they cover different failures, and a real API wires up both.

- **Responder-level errors** (`Option`, `Result`) - your handler *ran* and decided the outcome. "I looked up book 99, it doesn't exist, return `None`." This is a *domain* decision, and it belongs in the handler because only the handler knows the domain. (Phase 4.)
- **Catchers** (`#[catch(...)]`) - the request *failed before or outside* any handler's decision: no route matched, a data guard rejected a malformed body (→ 422), a request guard rejected the caller, or a handler panicked (→ 500). The handler can't shape these because, for most of them, it never ran.

The clean rule: **let handlers express domain outcomes with `Option`/`Result`; let catchers express framework failures with `#[catch]`.** When `show` returns `None`, Rocket turns it into a 404 - and your `#[catch(404)]` then renders the *body* for it. So the two even cooperate: the handler decides "this is a 404," the catcher decides "here's what a 404 looks like." Define the shape once, reuse it everywhere.

## Drive it from the terminal

With the server running (`cargo run`), exercise the whole resource with `curl`:

```bash
# Create a book - expect 201 Created and the new record with an id
curl -i -X POST http://127.0.0.1:8000/books \
  -H 'Content-Type: application/json' \
  -d '{"title":"Dune","author":"Herbert"}'

# List all - expect 200 and a JSON array
curl http://127.0.0.1:8000/books

# Show one - expect 200; try a missing id for a 404 with your JSON body
curl -i http://127.0.0.1:8000/books/1
curl -i http://127.0.0.1:8000/books/999

# Update - expect 200 and the updated record
curl -i -X PUT http://127.0.0.1:8000/books/1 \
  -H 'Content-Type: application/json' \
  -d '{"title":"Dune Messiah","author":"Herbert"}'

# Delete - expect 204 No Content; deleting again gives 404
curl -i -X DELETE http://127.0.0.1:8000/books/1

# Trip a catcher: a route that doesn't exist, and a malformed body
curl -i http://127.0.0.1:8000/bookz
curl -i -X POST http://127.0.0.1:8000/books \
  -H 'Content-Type: application/json' -d '{"title":"oops"'
```

*What just happened:* the first six calls walk a single book through its whole lifecycle - created (201), listed and shown (200), the missing-id show returning your catcher's JSON 404, updated (200), deleted (204), then a second delete proving the handler's own `Status::NotFound` path. The last two trip catchers: `/bookz` matches no route (framework 404), and the truncated body fails the `Json<NewBook>` guard before `create` runs (framework 422). Same JSON error shape for both, courtesy of `.register(...)`.

> 💡 This API keeps its data in a `Mutex<HashMap>`, which lives in memory and vanishes on restart. Everything above - the handlers, responders, catchers - stays the same when you swap that store for a real database via `rocket_db_pools` and a driver like `sqlx`; the route shapes and error handling don't move. The store is the detail; the resource is the design.

## Recap

- **A REST resource is five attribute-routed handlers over the managed store:** `list`, `show`, `create`, `update`, `delete` - each pulling `&State<AppState>`, path `id`, and `Json` bodies from its signature.
- **The return type carries the per-request outcome:** `Json<Vec<Book>>` and `Json<Book>` for reads, `Option<Json<Book>>` for free 404s, `(Status::Created, Json<Book>)` for a 201, and a bare `Status` for delete's 204/404.
- **Catchers handle framework-level failures** - no route matched, a data guard rejected the body, a guard rejected the caller, a panic. Define them with `#[catch(404)]`/`#[catch(422)]`/`#[catch(500)]` returning a JSON `Value`, and `.register("/", catchers![...])` them.
- **422, not 400, is Rocket's signal for a malformed or incomplete JSON body** - the `Json<T>` data guard fails before your handler runs, so only a `#[catch(422)]` can shape that response.
- **Use both layers, by their jobs:** handlers express domain outcomes with `Option`/`Result`; catchers express framework failures and standardize the error body. They cooperate - handler decides "this is a 404," catcher decides what a 404 looks like.

## Quick check

```quiz
[
  {
    "q": "A client POSTs a body that's valid JSON but missing the required \"author\" field. What status does Rocket return, and where do you shape that response?",
    "choices": ["400, inside the create handler", "422, with a #[catch(422)] catcher", "500, with a #[catch(500)] catcher", "404, with a #[catch(404)] catcher"],
    "answer": 1,
    "explain": "The Json<NewBook> data guard fails before create runs, and Rocket signals that with 422 Unprocessable Entity. Because the handler never runs, only a #[catch(422)] catcher can shape the response body."
  },
  {
    "q": "Your show handler returns None for a missing book and also has a #[catch(404)] registered. What does the client receive?",
    "choices": ["A 200 with an empty body - None means no error", "A compile error - you can't have both a None and a catcher for 404", "A 404 whose body is rendered by your #[catch(404)] catcher", "Two responses, one from each layer"],
    "answer": 2,
    "explain": "The handler's None tells Rocket 'this is a 404' (the domain decision); the #[catch(404)] catcher then renders the body for that 404. The two layers cooperate - outcome from the handler, error shape from the catcher."
  },
  {
    "q": "How do you attach catchers to a Rocket app?",
    "choices": [".mount(\"/\", catchers![...])", ".register(\"/\", catchers![...])", "Adding them to the routes![...] list", ".manage(catchers![...])"],
    "answer": 1,
    "explain": "Catchers are registered with .register(base, catchers![...]), parallel to how routes are added with .mount(base, routes![...]). The base path can scope catchers to a sub-tree of the app."
  }
]
```


---

# Testing & Configuration

You've grown the books API from a single attribute route into full CRUD with error catchers. Two questions turn a weekend project into something you'd let other people depend on: *how do I know a change didn't quietly break a route?* and *how do I run this somewhere other than my laptop, with the right port and settings?* This phase answers both.

📝 **Testing and configuration are the two things that make your app portable** - able to run somewhere other than the place you wrote it. A test runs your *real* app in a throwaway, in-process environment so you can poke it and check the answers. Configuration is how that same app behaves differently depending on where it runs - port 8000 on your machine, port 80 in production - without editing code. Both lean on the same trick: building your `Rocket` instance from a function you can call from anywhere.

## The test client: call your app without a server

The fear most people bring to testing a web app is that they'll have to start the server, fire real HTTP requests at `localhost`, then tear it all down - slow, flaky, full of "connection refused" when something didn't boot in time. Rocket sidesteps that.

📝 **Rocket's local `Client` runs your real application in-process and dispatches requests straight to it - no network, no port, no running server.** Hand it the same `Rocket` instance your `main` would launch, call `client.get("/books")`, and Rocket routes that request through your actual handlers, guards, and catchers, then hands you back a response. A function call wearing an HTTP costume - fast (milliseconds) and reliable (nothing to boot, nothing to crash).

There are two flavors. The **blocking** client (`rocket::local::blocking::Client`) is the friendliest for tests - no `async` ceremony. Here's the shape, using the books API:

```rust
use rocket::local::blocking::Client;
use rocket::http::Status;

#[test]
fn list_books_ok() {
    let client = Client::tracked(rocket()).expect("valid rocket");
    let res = client.get("/books").dispatch();
    assert_eq!(res.status(), Status::Ok);
    // res.into_json::<Vec<Book>>() to read the body back as typed data
}
```

*What just happened:* `Client::tracked(rocket())` takes your built application - `rocket()` returns the same builder your `#[launch]` uses (factored out in a moment) - and wraps it in a test client. `client.get("/books")` builds a request; `.dispatch()` runs it through the app and returns the response. We assert on `res.status()`: did it succeed, redirect, 404? The `tracked` part means the client remembers cookies across requests, which matters for login or sessions - `Client::untracked(...)` exists for when you don't care.

To check the *body*, not the status, read it back as typed data:

```rust
#[test]
fn list_books_returns_json() {
    let client = Client::tracked(rocket()).expect("valid rocket");
    let res = client.get("/books").dispatch();

    let books = res.into_json::<Vec<Book>>().expect("valid book list");
    assert!(books.iter().any(|b| b.title == "Dune"));
}
```

*What just happened:* `res.into_json::<Vec<Book>>()` deserializes the response body into your real `Book` type - the same `serde` derive your handlers use, now working in reverse. You get back actual Rust values to assert against: "the list contains a book titled Dune." If the body isn't valid JSON for that type, `into_json` returns `None` and `.expect(...)` fails loudly.

Writing data is the same idea with a request body attached:

```rust
#[test]
fn create_book_returns_201() {
    let client = Client::tracked(rocket()).expect("valid rocket");
    let new_book = NewBook { title: "Hyperion".into(), author: "Simmons".into() };

    let res = client.post("/books").json(&new_book).dispatch();

    assert_eq!(res.status(), Status::Created);
    let created = res.into_json::<Book>().expect("returns the created book");
    assert_eq!(created.title, "Hyperion");
}
```

*What just happened:* `client.post("/books").json(&new_book)` serializes your `NewBook` to JSON and sets `Content-Type: application/json` - the mirror image of the `Json<NewBook>` data guard. We assert the status is `201 Created` and that the response echoes back the created book. This single test exercises routing, the JSON data guard, handler logic, and the responder - the whole vertical slice, no server in sight.

> 💡 There's also an async client, `rocket::local::asynchronous::Client`, same API but `.dispatch().await`. Reach for it when the test itself needs to be `async`. For most route tests the blocking client is less ceremony - start there.

If you've never written a Rust test before - `#[test]`, `assert_eq!`, `cargo test`, Arrange-Act-Assert - see [Your First Unit Test](/guides/your-first-unit-test), and [Testing in CI](/guides/testing-in-ci) for wiring `cargo test` into a pipeline.

## Factor the builder so tests and `main` share one app

Every test above calls `rocket()`. That isn't a coincidence - it's the load-bearing pattern of the whole phase. ⚠️ **If your `#[launch]` function builds the app inline, your tests can't get at it - they'd have to duplicate the build, and a duplicate drifts.** The instant your test app is configured even slightly differently from the real one, your tests are lying to you.

The fix is to build the app in *one* place that both `main` and the tests call:

```rust
use rocket::{Build, Rocket};

fn rocket() -> Rocket<Build> {
    rocket::build()
        .mount("/", routes![list_books, get_book, create_book, delete_book])
        .register("/", catchers![not_found, unprocessable])
        .manage(BookStore::new())
}

#[launch]
fn launch() -> Rocket<Build> {
    rocket()
}
```

*What just happened:* the real assembly - mounting routes, registering catchers, managing state - now lives in `rocket()`, which returns a `Rocket<Build>` (the "configured but not yet launched" stage). `#[launch]` does nothing but call it. Production launches exactly what your tests dispatch against, down to the last catcher. 📝 Same principle as Flask's app factory: one function builds the app, and everything - tests, `main`, later a benchmark harness - asks *it* for a fresh instance. One source of truth, no drift.

## Configuration: `Rocket.toml`, profiles, and env vars

Your app shouldn't hard-code where it runs. Port 8000 is fine on your laptop; production might want port 80, a different bind address, quieter logs. Rocket handles this through **Figment**, its layered configuration system: mostly a `Rocket.toml` file plus environment variables.

📝 **Rocket reads `Rocket.toml` from your project root, split into *profiles* - named sections like `[default]`, `[debug]`, and `[release]` - and the active profile is chosen by how you built the app.** A debug build (`cargo run`) uses `debug`; a release build (`cargo run --release`) uses `release`. `[default]` applies to *every* profile as a baseline, and the active profile overrides it.

```toml
[default]
address = "0.0.0.0"
port = 8000

[release]
port = 80
```

*What just happened:* `[default]` sets the baseline - bind to `0.0.0.0` (all interfaces) on port 8000 - so a normal `cargo run` serves on 8000. Build with `--release` and Rocket layers `[release]` on top, overriding the port to 80 while keeping the inherited `address`. One file describes both environments; the build flag picks which one is live. No `if cfg!(debug)` branching in your code.

The second layer is environment variables, sitting *on top* of the file:

```bash
ROCKET_PORT=9000 cargo run
```

*What just happened:* Rocket reads any `ROCKET_`-prefixed variable as a config override, so this runs on port 9000 regardless of `Rocket.toml` - the env var wins. Common ones: `ROCKET_PORT`, `ROCKET_ADDRESS`, `ROCKET_LOG_LEVEL` (try `=debug` when something's misbehaving, `=off` to silence it). 💡 This precedence - env vars over file over defaults - is exactly what deployment wants: bake sane values into `Rocket.toml`, then let the host override the few that differ (the port a platform assigns you) without rebuilding.

You can also add your *own* keys and read them as a typed struct:

```rust
use serde::Deserialize;

#[derive(Deserialize)]
struct AppConfig {
    catalog_name: String,
    max_books: usize,
}

// inside a fairing or AdHoc::config, given the built `rocket`:
let app_config: AppConfig = rocket.figment().extract().expect("valid app config");
```

*What just happened:* you put `catalog_name` and `max_books` under a profile in `Rocket.toml`, and `rocket.figment().extract()` deserializes the *whole* active configuration into your `AppConfig` struct - your custom keys plus Rocket's own. Same `serde` machinery as your JSON bodies, pointed at config instead. In practice you do this in a fairing (Phase 5) or via `AdHoc::config::<AppConfig>()`, so the parsed config becomes managed state your handlers pull in with `State<AppConfig>`. Type-checked at startup, no scattered `env::var` calls.

## Production: build for release and let the host override

Shipping the books API comes down to a few moves, and you've already met most of the pieces.

**Build for release**: `cargo build --release` compiles with optimizations into `target/release/`, and running that binary activates the `release` profile from `Rocket.toml` - the port-80 setting and any other release tuning come along automatically. **Let the host override what's environment-specific** through `ROCKET_*` vars: a platform that hands you a port at runtime sets `ROCKET_PORT`, and your app obeys without a rebuild.

To run the same everywhere, wrap it in a container. Rust compiles to a single binary, so use a **multi-stage build**: one stage compiles, a tiny final stage carries only the binary.

```dockerfile
FROM rust:1.82 AS build
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bookworm-slim
WORKDIR /app
COPY --from=build /app/target/release/books-api .
COPY Rocket.toml .
ENV ROCKET_ADDRESS=0.0.0.0
CMD ["./books-api"]
```

*What just happened:* the `build` stage has the full Rust toolchain and compiles your release binary; the final image starts from a slim Debian base and copies *only* the compiled binary and `Rocket.toml` across - the multi-gigabyte compiler toolchain never ships. `ENV ROCKET_ADDRESS=0.0.0.0` makes the server listen on all interfaces inside the container (the default `127.0.0.1` only accepts connections from *within* it). The result is a small, self-contained image that runs identically on your laptop and a cloud host.

⚠️ **Rocket is an application server, not a front door.** In production put a **reverse proxy** (nginx or your platform's load balancer) in front of it to terminate TLS and shield the app from raw internet traffic - Rocket serves plain HTTP behind it. For the full walk from "binary that runs" to "live on a domain with HTTPS," see [Ship Your Side Project](/guides/ship-your-side-project).

💡 Clean testing and clean configuration are the *same* capability wearing two hats: your app is a value you build from one function and run in different contexts - dispatched in-process by a test `Client`, or launched with a release profile behind a proxy. The `rocket()` function you factored out is what makes the app portable enough to ship.

## Recap

1. 📝 **The local `Client` dispatches requests to your real app in-process** - no server, no port. Use `rocket::local::blocking::Client`, `Client::tracked(rocket())`, then `client.get(...).dispatch()`. Assert on `res.status()`; read the body with `res.into_json::<T>()`.
2. **POST tests send a body with `.json(&value)`**, which serializes it and sets the JSON content type - the mirror of your `Json<T>` data guard. There's also an async client (`rocket::local::asynchronous::Client`) for when the test itself is `async`.
3. ⚠️ **Factor the build into a `fn rocket() -> Rocket<Build>`** that both `#[launch]` and your tests call, so production and tests run the *same* app. Inline-built apps force a duplicate that drifts.
4. 📝 **Configure with `Rocket.toml` profiles** (`[default]` baseline, `[debug]`, `[release]`); the build flag (`--release`) picks the active profile. **`ROCKET_*` env vars override the file** (`ROCKET_PORT`, `ROCKET_ADDRESS`, `ROCKET_LOG_LEVEL`) - precedence is env > file > defaults.
5. **Custom config keys** parse into your own struct via `rocket.figment().extract()`, usually in a fairing or `AdHoc::config`, becoming managed state.
6. **Production:** `cargo build --release` activates the `release` profile; let the host set `ROCKET_*`; ship a multi-stage container carrying only the binary; put a reverse proxy in front for TLS.

## Quick check

Three questions on the ideas that matter most before you ship:

```quiz
[
  {
    "q": "What does Rocket's local Client (rocket::local::blocking::Client) let you do?",
    "choices": [
      "Dispatch requests to your real application in-process - through your actual routes, guards, and catchers - with no server, port, or network",
      "Start a real server on a random port and send it HTTP requests over the loopback network",
      "Generate test cases automatically for every mounted route",
      "Connect your tests to the production database so they exercise real rows"
    ],
    "answer": 0,
    "explain": "The local Client runs your built Rocket instance in-process and dispatches requests straight to it - no server, no port, no network. You assert on res.status() and read bodies with res.into_json::<T>(). That's what makes the tests fast and reliable."
  },
  {
    "q": "Why factor app construction into a function like fn rocket() -> Rocket<Build> that both #[launch] and your tests call?",
    "choices": [
      "So tests and production build the exact same app from one source of truth, with no duplicated setup that can drift apart",
      "Because #[launch] is not allowed to call .mount() or .manage() directly",
      "Because the local Client can only accept a function named rocket()",
      "To make the release build smaller by removing the launch macro"
    ],
    "answer": 0,
    "explain": "If #[launch] builds the app inline, tests have to duplicate the build - and any drift between the two means your tests no longer reflect production. One rocket() function that both call keeps them identical."
  },
  {
    "q": "In Rocket's configuration, what overrides a value set in Rocket.toml?",
    "choices": [
      "A matching ROCKET_* environment variable - env vars sit on top of the file, so ROCKET_PORT=9000 wins over the file's port",
      "Nothing; values in Rocket.toml are final and cannot be overridden at runtime",
      "The [default] profile always wins over every other profile and over env vars",
      "Command-line flags passed to cargo run, which Rocket parses directly"
    ],
    "answer": 0,
    "explain": "Rocket layers config via Figment with precedence env > file > defaults. A ROCKET_-prefixed env var (e.g. ROCKET_PORT=9000) overrides whatever Rocket.toml says, which is exactly what you want for letting a host set the port at deploy time without a rebuild."
  }
]
```


---

# Where to Go Next

Look at what you can actually do now. Write `#[get("/books/<id>")]` above a function and have a server route to it, pull pieces out of a request through dynamic path segments and query params, gate a handler behind a **request guard** that must succeed before the function runs, accept and return `Json<T>`, build custom **responders**, share a store across handlers with **managed state** and `.manage(...)`, wrap the whole thing in a **fairing**, serve full CRUD with `#[catch(404)]` **error catchers**, and test it with the local client before shipping it with a `Rocket.toml` and config profiles. That is a real REST API, not a toy.

Rocket's "magic" is no longer magic to you. You know the throughline cold: an **attribute is the route**, the **function signature is the request** (path, query, body, and guards), the **return type is the response**, and macros wire it together. The day something misbehaves, you'll know which of those three pieces to look at.

This last phase isn't more handlers. It's the map: where Rocket sits among the other Rust web frameworks, the layer you'll almost certainly add next, the roots worth learning, and one concrete thing to go build.

## Rocket 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 -- Most ergonomic, macro-driven --> Rocket[Rocket]
  Q -- Tower ecosystem + type-safe extractors --> Axum[axum]
  Q -- Max performance + maturity --> Actix[actix-web]
  Rocket --> Note[You are here]
```

A line on each:

- **Rocket** - the most **ergonomic** of the three, leaning hard on macros to make handler code wonderfully concise: request guards, error catchers, fairings, and managed state come batteries-included, and routes read almost like Flask. If you want the least ceremony and the most readable code, this is your style. (You are here.)
- **axum** - the modern, tower-native default from the Tokio team, driven by the type system: plain `async fn` handlers, extractors as arguments, `IntoResponse` returns. Its quiet superpower is the **tower** ecosystem - reusable middleware and a clean path to gRPC. See [axum From Zero](/guides/axum-from-zero).
- **actix-web** - the mature, batteries-included heavyweight, consistently at or near the **top of the performance benchmarks**. If raw throughput and a long track record matter most, this is your pick. See [actix-web From Zero](/guides/actix-web-from-zero).

> 💡 How to pick: reach for **Rocket** when you want the most concise, approachable Rust web code. Reach for **axum** when you want the tower ecosystem and type-safe extractors. Reach for **actix-web** when you want raw performance and maturity.

📝 A plain note worth carrying: Rocket's release cadence has been slower than axum's, and axum has more community momentum right now. None of that makes Rocket a wrong choice - Rocket 0.5 is stable, fully async, and a genuine joy for app code. The senior instinct isn't memorizing a winner; it's asking "best for *this* job?" and answering straight. 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. That's perfect for learning and useless in production - restart the server and the data is gone. The very next thing almost every real Rocket service grows is a **database**.

Rocket has a tidy answer here: **`rocket_db_pools`**. It integrates a connection pool (backed by `sqlx`, SeaORM, or Deadpool) with Rocket's own machinery - the pool lives as **managed state** and is configured straight from your `Rocket.toml`, the same config system you met in Phase 7. You add a small derive, name your database in config, and then a pool connection becomes something a handler can ask for, right alongside the guards and state you already use.

If you'd rather keep it lean, you can also drop a plain **`sqlx::Pool`** into managed `State` yourself and pull it out with `State<T>` - the exact pattern from Phase 5, with a connection pool in the slot where your in-memory store used to sit.

A word on the data layer itself, because Rust has **no single default ORM**:

- **`sqlx`** - not an ORM at all, but the most popular companion. You write **raw SQL**, and a macro checks your queries **against a real database at compile time** - a typo'd column is a build error, not a 500 in production. Fully async, fits Rocket cleanly.
- **SeaORM** - a proper **async ORM** built on top of sqlx, for when you want 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.

The reassuring bit: your handlers barely change. That Phase 5 investment in managed state pays off - you're swapping the bottom layer, not rewriting the top.

## The roots: Tokio

Rocket is async all the way down, and the thing actually driving every `.await` in your handlers is **Tokio** - the runtime that schedules tasks and handles the I/O. You've been standing on it the whole time, often without naming it. You don't need to know Tokio to ship, but the day you want to understand *why* an async handler behaves the way it does, that's where the floor drops away. See [Tokio: The Async Runtime](/guides/tokio-the-async-runtime).

## What to build

Reading more won't make this stick. Building one real thing will. Take the **books API** you grew across this guide and carry it all the way home:

- **Swap the in-memory store for `rocket_db_pools` + sqlx** so the books survive a restart. Name the database in `Rocket.toml`, write a few compile-checked queries, and watch your handlers stay almost exactly as they were.
- **Add real auth via a `FromRequest` guard** - a JWT or session check that runs before the handler, so each request proves who it is and books belong to a user. This is the request-guard pattern from Phase 3, aimed at a real job.
- **Add a fairing** for CORS or request logging, the middleware pattern from Phase 5 doing actual production work.
- **Generate API docs** with OpenAPI via **okapi** (or utoipa), so other people - and future you - can read the contract.
- **Tidy up config** with profiles, so secrets, the database URL, and the port come from the environment, not hardcoded values.
- **Deploy it** somewhere you can hit from your phone.

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, guards, state, fairings, catchers, tests, config, deploy. Finishing one project completely teaches more than three more tutorials would.

## The clear-eyed close

Rocket was never magic. Strip it back and it's a handful of ideas you now understand completely: the **attribute is the route**, the **signature is the request**, the **return type is the response** - plus **guards** that gate the handler, **managed state** it can reach for, **fairings** that wrap it, and **catchers** that handle what goes wrong. Macros wire those together, and underneath it's plain async Rust on Tokio, checked by the compiler.

That's why you can read the machine now. Go finish the books API, give it a real database through `rocket_db_pools`, lock it behind a `FromRequest` guard, wrap it in a fairing, deploy it, and show someone. You're ready.

## Recap

1. **You can ship a real Rocket API** - attribute routes, dynamic paths, guards and data, responders, managed state and fairings, CRUD with catchers, tested and configured - and you understand the throughline: attribute = route, signature = request, return type = response.
2. **Choose a framework on purpose** - Rocket for the most concise, ergonomic, macro-driven code; axum for the tower ecosystem and type-safe extractors; actix-web for raw performance and maturity. Rocket's cadence is slower and axum has more momentum, but Rocket 0.5 is stable and a joy for app code.
3. **A database is the next layer** - `rocket_db_pools` wires a pool into managed state and `Rocket.toml` config, or drop a `sqlx::Pool` into `State` yourself. Rust has no single default ORM: sqlx (compile-checked raw SQL), SeaORM (async ORM), or Diesel (mature ORM).
4. **Tokio is the root** - the async runtime driving every `.await` in your handlers; learn it to remove the last of the magic.
5. **Build and finish one thing** - carry the books API to `rocket_db_pools` + sqlx, a JWT/session `FromRequest` auth guard, a logging/CORS fairing, OpenAPI docs, config profiles, 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 the most concise, approachable Rust web code, with batteries-included guards, catchers, and fairings. Which framework fits on purpose?",
    "choices": [
      "actix-web, because it's the fastest",
      "axum, because it's tower-native",
      "Rocket, the most ergonomic and macro-driven of the three",
      "None of them support middleware"
    ],
    "answer": 2,
    "explain": "Rocket leans on macros for the most concise, readable handler code, with request guards, error catchers, and fairings built in. Pick axum for the tower ecosystem and type-safe extractors, actix-web for raw performance and maturity."
  },
  {
    "q": "What does rocket_db_pools give you when you add a database to a Rocket app?",
    "choices": [
      "It replaces your handlers with auto-generated CRUD",
      "It integrates a connection pool (sqlx/SeaORM/Deadpool) as managed state, configured from Rocket.toml",
      "It only works with Diesel and forces synchronous queries",
      "It removes the need for any SQL at all"
    ],
    "answer": 1,
    "explain": "rocket_db_pools wires a connection pool into Rocket's managed state and reads its config from Rocket.toml - the same state and config systems from Phases 5 and 7. You still write queries; the pool just drops into the slot your in-memory store used to fill."
  },
  {
    "q": "What is the real situation with ORMs in Rust for a Rocket API?",
    "choices": [
      "Rocket ships its own official ORM you must use",
      "There's no single default - sqlx (compile-checked raw SQL), SeaORM (async ORM on top of sqlx), 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 built on it, and Diesel is the mature, more sync-flavored option. You choose based on the job."
  }
]
```
