Request bodies and responses
import 'package:gisila/gisila.dart';The generated code calls these helpers for you based on parameter types (see parameter binding rules). Call them yourself in routes that take a raw Request, in middleware, or in hand-written Shelf handlers. All of them throw BadRequestException for missing or malformed input, which the framework renders as a 400.
Reading bodies
Section titled “Reading bodies”readBody
Section titled “readBody”Future<DecodedBody> readBody(Request request, {bool required = true})Decodes the body according to its Content-Type.
| Content-Type | Returns |
|---|---|
application/json, application/vnd.api+json, text/json |
Whatever jsonDecode produces. |
application/x-www-form-urlencoded, multipart/form-data |
A FormData. |
| missing | null when required is false, otherwise 400. |
| anything else | 400 (Unsupported Content-Type). |
When to use: when a route accepts more than one body format and you want one call that handles all of them.
readJsonBody
Section titled “readJsonBody”Future<Object?> readJsonBody(Request request, {bool required = true})Reads the body as JSON regardless of Content-Type. An empty body is null when required is false, otherwise a 400; malformed JSON is a 400.
When to use: JSON-only endpoints, or when you need a JSON array.
readFormBody
Section titled “readFormBody”Future<FormData?> readFormBody(Request request, {bool required = true})Reads the body strictly as url-encoded or multipart form data. Returns null only when there is no Content-Type and required is false. Any other content type is a 400.
When to use: upload endpoints and HTML form posts.
DecodedBody
Section titled “DecodedBody”typedef DecodedBody = Object?;Return type of readBody: a Map (JSON object), a List (JSON array), a FormData, a JSON primitive, or null.
parseFormData
Section titled “parseFormData”Future<FormData> parseFormData({ required Map<String, String> headers, required Future<String> Function() body, required Stream<List<int>> Function() bytes, int maxParts = defaultMaxFormParts, int maxFieldBytes = defaultMaxFormFieldBytes,})The parser behind readFormBody and bindForm. It takes the pieces of a request instead of a Request, so you can test it without Shelf. Multipart bodies are capped at maxParts parts and maxFieldBytes per text field; going over either is a 400, as is a text field that is not valid UTF-8 or a multipart body without a boundary. The overall body size is capped by AppConfig.maxRequestBodyBytes / RouteConfig.maxBodyBytes.
When to use: when you need stricter limits than the defaults:
final form = await parseFormData( headers: request.headers, body: request.readAsString, bytes: request.read, maxParts: 20, maxFieldBytes: 64 * 1024,);| Constant | Value | Description |
|---|---|---|
defaultMaxFormParts |
1000 |
Default maxParts. |
defaultMaxFormFieldBytes |
1048576 (1 MiB) |
Default maxFieldBytes. |
formUrlEncodedContentType |
ContentType('application', 'x-www-form-urlencoded') |
A dart:io ContentType. |
multipartFormDataContentType |
ContentType('multipart', 'form-data') |
A dart:io ContentType. |
FormData
Section titled “FormData”The text fields and files of a form request. It is also an immutable Map<String, Object> (text fields as String, files as UploadedFile), so it works where a Map<String, dynamic> body is expected.
class FormData with MapMixin<String, Object> { FormData({ required Map<String, String> fields, required Map<String, UploadedFile> files, }); Map<String, String> get fields; Map<String, UploadedFile> get files;}When to use: as a controller parameter type when you need several files or a mix of text and files and don’t want a Form. Writing to it throws UnsupportedError. When a key is both a field and a file, form[key] returns the field.
@Post('/imports')@Permissions({'data:write'})Future<Map<String, Object?>> importCsv(FormData body, ImportService imports) async { final mode = body.fields['mode'] ?? 'append'; final csv = body.files['rows'] ?? (throw BadRequest('Missing "rows" file')); return imports.importRows(await csv.readAsBytes(), mode: mode);}UploadedFile
Section titled “UploadedFile”One uploaded file from a multipart body.
class UploadedFile { UploadedFile(String name, ContentType contentType, Stream<List<int>> byteStream); final String name; final ContentType contentType; Future<List<int>> readAsBytes(); Stream<List<int>> openRead();}| Member | Description |
|---|---|
name |
The file name the client reported. Never use it as a path without sanitising it. |
contentType |
The part’s Content-Type (text/plain when absent or unparsable). Client-supplied. |
readAsBytes() |
Reads the whole file into memory. The stream can be consumed only once. |
openRead() |
The raw byte stream, for piping to disk or object storage without buffering. Single-use. |
When to use: as a controller parameter named after the multipart field (UploadedFile file reads field file), or through a FileField.
@Post('/avatars/{id}')@Permissions({'profile:write'})Future<Map<String, Object?>> uploadAvatar(String id, UploadedFile file) async { final bytes = await file.readAsBytes(); return {'filename': file.name, 'size': bytes.length, 'type': file.contentType.mimeType};}Parameters
Section titled “Parameters”coerce
Section titled “coerce”T coerce<T>(String? value, String paramName, {required bool required})Parses a raw path, query or header string into T: String, int, double, num, bool (true/1/yes, false/0/no, any case) or DateTime (DateTime.parse), nullable or not.
null, or an empty string for any type butString, is missing: a400(Missing required parameter) whenrequiredistrueandTis non-nullable, otherwisenull.- An unparsable value is a
400naming the parameter. - Any other
Tis a400(Unsupported parameter type).
When to use: reading query parameters by hand in middleware or a raw handler.
final page = coerce<int?>(request.url.queryParameters['page'], 'page', required: false) ?? 1;Responses
Section titled “Responses”jsonResponse
Section titled “jsonResponse”Response jsonResponse({ Object? body, int statusCode = 200, Map<String, String>? extraHeaders,})JSON-encodes body and returns a response with content-type: application/json; charset=utf-8. Values are converted recursively: objects through their toJson(), DateTime as ISO 8601, enums without toJson() as their name, other objects without toJson() as toString().
When to use: in routes that return a Response so they can add headers, choose a status at runtime, or both.
@Post('/sessions')@Public()Future<Response> login(LoginForm form, AuthService auth) async { final session = await auth.login(form.email.value!, form.password.value!); return jsonResponse( body: session.user, extraHeaders: {'set-cookie': 'sid=${session.id}; HttpOnly; Secure; SameSite=Lax; Path=/'}, );}emptyResponse
Section titled “emptyResponse”Response emptyResponse({int statusCode = 204})An empty response with content-length: 0. Generated code uses it for void methods.