Request Guards & Data
Here is the mental model that makes the rest of Rocket click into place. 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
POSTs. There is exactly one data parameter per route, and you mark it with thedata = "<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. You do not write if not authenticated: return 401 at the top of every handler. You add a parameter of type User, and Rocket guarantees the body
only runs once that User exists. We will build exactly that below.
📝 You met one guard already without us naming it: in Phase 2,
id: usizein#[get("/books/<id>")]was a guard backed byFromParam. 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:
use ;
use Json;
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
dataparameter. 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.
use Form;
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
This is the idea worth slowing down for. 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.
Let us build a tiny API-key guard. We want /admin to run only when the request carries the header
x-api-key:
use ;
use Status;
;
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
ApiKeyin the signature is the entire auth check. Swap it for a realUserguard that validates a session cookie and looks the user up, and every handler that takesuser: Useris automatically logged-in-only — no boilerplateifat 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
Userguard that assumes aDbconnection guard ran), list the dependency first. And remember the one rule from earlier: the singledataparameter 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)]andcargo 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; iffrom_requestreturnsErrororForward, the handler never runs (e.g. a missing API key yields401). - Guards make auth a type in the signature — add a
Userparameter 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:
[
{
"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."
}
]
← Phase 2: Routing & Dynamic Paths · Guide overview · Phase 4: Responders →
Check your understanding
1. How many `data` parameters can a single Rocket route have?
2. 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?
3. Which trait must a type implement to be usable as a request guard?