Requests
Every handler receives a Request. Start with input() for application data;
it gives you one consistent API whether the value arrived in JSON, a form body,
or the query string:
Route.post('/posts', (Request request) { final title = request.input('title'); final page = request.integer('page', 1); return {'title': title, 'page': page};});Request Information
request.method; // 'POST'request.uri; // the full Urirequest.path; // '/api/posts'request.ip; // the client addressrequest.rawBody; // the body exactly as receivedBuffered bodies are limited to 10 MB by default. Set
http.max_body_bytes (the skeleton reads HTTP_MAX_BODY_BYTES) to change the
limit. A larger body receives 413 Payload Too Large; the limit is enforced on
bytes read, even when Content-Length is absent.
Headers
request.header('content-type'); // case-insensitiverequest.headers; // unmodifiable, keys lower-casedrequest.bearerToken(); // the token from `Authorization: Bearer ...`, or nullThree helpers describe what the client wants:
| Getter | True when |
|---|---|
isJson |
the request’s content-type contains json |
wantsJson |
the accept header asks for JSON, or the path starts with /api |
expectsJson |
either of the above |
expectsJson decides whether errors are rendered as JSON or as HTML. See Error Handling.
Public URLs Behind a Proxy
request.uri describes the connection received by the Maat process. Behind a
reverse proxy, use request.publicUri when generating an absolute URL so the
original scheme and host can be restored:
final canonical = request.publicUri.replace(path: '/posts/42');Forwarded headers are accepted only from addresses listed in
app.trusted_proxies. Leave that setting empty unless the application is
actually behind a known proxy; trusting arbitrary x-forwarded-host values can
let a client influence links generated by your application.
Retrieving Input
input reads from the body first, then the query string:
request.input('title'); // null when absentrequest.input('title', 'Untitled'); // with a defaultrequest.input(); // every value, mergedDot notation reaches into nested JSON, including through lists:
// {"author": {"name": "Ada"}, "tags": ["a", "b"]}request.input('author.name'); // 'Ada'request.input('tags.0'); // 'a'Query Strings
request.query('page'); // query string onlyrequest.query('page', '1');request.queryAll; // Map<String, String>Typed Accessors
request.string('title'); // String?request.integer('page'); // int?, null when unparseablerequest.integer('page', 1); // with a defaultrequest.boolean('active'); // true for true, 1, '1', 'on', 'yes'Numeric values are treated as true when they are non-zero.
Slices of Input
request.all(); // everything, body over queryrequest.only(['title', 'body']);request.except(['_token']);Presence
request.has('title'); // the key exists, even if its value is nullrequest.filled('title'); // the key exists and is not null or emptyhas distinguishes “absent” from “present and null”, which matters when a client deliberately sends null to clear a field.
The JSON Body
request.json; // the decoded body: a Map, a List, or nulljson is parsed once and cached. A malformed body yields null rather than
throwing, so a bad request does not become a 500. A top-level JSON array is
available through json, but it does not become named input for input().
mergeandreplacechange whatall()andinput()return, but do not invalidate the cachedjson. Read input throughinput()/all()if anything upstream may have modified it.
Streaming a Request Body
Large uploads can bypass buffering explicitly:
Route.post('/upload', (Request request) async { final file = File('storage/upload.bin').openWrite(); await request.bodyStream.pipe(file); return Response.noContent();}).streamRequestBody();The byte limit still applies while the stream is consumed. On a streaming
route, use bodyStream; rawBody, input() and json require a buffered body.
Modifying Input
request.merge({'user_id': '7'}); // add or overwrite keysrequest.replace({'title': 'New'}); // discard existing inputMiddleware use this to normalise input before it reaches your handler — TrimStrings is built on it.
Route Parameters
request.param('id'); // a single parameter, or nullrequest.params; // every parameterrequest.route; // the RouteDefinition that matchedAttributes
attributes is a scratch map that travels with the request. Middleware use it to hand values to handlers further down the pipeline:
// in middlewarerequest.attributes['user'] = user;
// in the handlerfinal user = request.attributes['user'] as User;Validating Input
final data = await request.validate({ 'title': 'required|string|max:120', 'body': 'required|string',});validate returns only the validated keys, and throws a ValidationException — rendered as a 422 — when the input fails. It does not modify the request’s input. See Validation.
Building Requests by Hand
Request.create builds a request without a socket, which is what the test client uses:
Request.create( method: 'POST', path: '/api/posts?page=2', json: {'title': 'Hello'},);
Request.create(method: 'POST', path: '/login', form: {'email': 'a@b.c'});Request.create(path: '/', headers: {'accept': 'application/json'});| Parameter | Default | Description |
|---|---|---|
method |
'GET' |
The HTTP verb. |
path |
'/' |
Path, optionally with a query string. |
headers |
{} |
Request headers. |
body |
A raw body string. | |
json |
An object encoded as JSON, setting the content type. | |
form |
Form fields, URL-encoded. | |
ip |
'127.0.0.1' |
The client address. |