Responses
A Response object represents the HTTP response your server sends back to the client. It encapsulates the status code, headers, and body content that together form the complete answer to a client's request.
In Relic, handlers return a Response as part of the Result type. The response tells the client whether the request succeeded, failed, or requires further action, and provides any requested data or error information.
Understanding HTTP responses
An HTTP response consists of three main parts:
- Status Code - A three-digit number indicating the outcome (e.g., 200 for success, 404 for not found)
- Headers - Metadata about the response (content type, caching rules, cookies, etc.)
- Body - The actual content being sent (HTML, JSON, files, or empty)
Response convenience methods
Relic's Response class provides static convenience methods for common HTTP status codes, making it easy to create appropriate responses without memorizing numeric codes. Instead of writing Response(400), you can simply use Response.badRequest().
Creating responses
Success responses
Success responses (2xx status codes) indicate that the request was received, understood, and processed successfully.
The most common response indicates the request succeeded and returns the requested data:
app.get('/info', (final req) {
final method = req.method; // Method.get
return Response.ok(
body: Body.fromString('Received a ${method.name} request'),
);
});
Error responses
Error responses (4xx, 5xx status codes) indicate that the request was invalid or cannot be fulfilled due to server or client side issues.
400 Bad Request
The request is malformed or contains invalid data:
app.post('/api/users', (final req) async {
try {
final bodyText = await req.readAsString();
final data = jsonDecode(bodyText) as Map<String, dynamic>;
final name = data['name'] as String?;
final email = data['email'] as String?;
if (name == null || email == null) {
return Response.badRequest(
body: Body.fromString(
jsonEncode({'error': 'Name and email are required'}),
mimeType: MimeType.json,
),
);
}
// Create a mock user object with generated data.
final newUser = {
'id': DateTime.now().millisecondsSinceEpoch,
'name': name,
'email': email,
'created_at': DateTime.now().toIso8601String(),
};
return Response.ok(
body: Body.fromString(
jsonEncode({'message': 'User created', 'user': newUser}),
mimeType: MimeType.json,
),
);
} catch (e) {
return Response.badRequest(
body: Body.fromString(
jsonEncode({'error': 'Invalid JSON: $e'}),
mimeType: MimeType.json,
),
);
}
});
Custom status codes
For status codes without a dedicated constructor, use the general Response constructor:
app.get('/teapot', (final req) {
return Response(
418, // I'm a teapot
body: Body.fromString('I refuse to brew coffee'),
);
});
For a comprehensive list of HTTP status codes, check out Mozilla's HTTP Status Codes documentation.
To ensure consistency and avoid memorizing numeric codes, use Relic's convenient response constructors like Response.ok(), Response.badRequest(), and Response.notFound() etc.
Working with response bodies
The response body contains the actual data you're sending to the client. Relic's Body class provides a unified way to handle different content types.
Text responses
For plain text responses, use Body.fromString():
app.get('/info', (final req) {
final method = req.method; // Method.get
return Response.ok(
body: Body.fromString('Received a ${method.name} request'),
);
});
By default, Relic infers the MIME type from the content. For plain text, it sets text/plain.
HTML responses
To serve HTML content, specify the MIME type explicitly:
app.get('/page', (final req) {
final pageNum = req.queryParameters.raw['page'];
if (pageNum != null) {
final page = int.tryParse(pageNum);
if (page == null || page < 1) {
return Response.badRequest(
body: Body.fromString('Invalid page number'),
);
}
}
final html = '''
<!DOCTYPE html>
<html>
<head><title>My Page</title></head>
<body>
<h1>Welcome!</h1>
<p>Page: ${pageNum ?? "1"}</p>
</body>
</html>
''';
return Response.ok(body: Body.fromString(html, mimeType: MimeType.html));
});
JSON responses
For JSON APIs, encode your data and specify the JSON MIME type:
app.get('/users/:id', (final req) {
final id = req.pathParameters.raw[#id]!;
final matchedPath = req.matchedPath;
final fullUri = req.url;
log('Matched path: $matchedPath, id: $id');
log('Full URI: $fullUri');
// Create a mock user object for the response and return it as JSON.
final user = {
'id': int.tryParse(id),
'name': 'User $id',
'email': 'user$id@example.com',
};
return Response.ok(
body: Body.fromString(jsonEncode(user), mimeType: MimeType.json),
);
});
Binary data
For images, PDFs, or other binary content, use Body.fromData():
app.get('/image.png', (final req) {
final imageBytes = Uint8List.fromList([1, 2, 3, 4]); // Mock image data.
return Response.ok(body: Body.fromData(imageBytes));
});
Relic automatically infers the MIME type from the binary data when possible.
Streaming responses
For large files or generated content, stream the data instead of loading it all into memory:
app.get('/large-file', (final req) {
final dataStream = Stream.fromIterable([
Uint8List.fromList([1, 2, 3]),
Uint8List.fromList([4, 5, 6]),
]);
return Response.ok(
body: Body.fromDataStream(
dataStream,
mimeType: MimeType.octetStream,
contentLength: 6, // Optional but recommended.
),
);
});
Empty responses
Some responses don't need a body. Use Body.empty() or simply omit the body parameter:
app.get('/empty1', (final req) {
// Return an explicitly empty response body.
return Response.ok(body: Body.empty());
});
app.get('/empty2', (final req) {
// Use noContent() for a 204 status with no body.
return Response.noContent();
});
Setting response headers
Headers provide metadata about your response. Use the Headers class to build type-safe headers:
app.get('/api/data', (final req) {
final headers = Headers.build((final h) {
// Configure cache control headers.
h.cacheControl = CacheControlHeader(maxAge: 3600, publicCache: true);
// Add a custom application header.
h['X-Custom-Header'] = ['value'];
});
return Response.ok(
headers: headers,
body: Body.fromString(
jsonEncode({
'status': 'ok',
'timestamp': DateTime.now().toIso8601String(),
}),
mimeType: MimeType.json,
),
);
});
For a comprehensive list of HTTP headers, check out Mozilla's HTTP Headers documentation.
Final tips
Effective HTTP responses are the foundation of reliable web applications. Beyond just sending data back to clients, responses communicate the outcome of operations, guide client behavior, and provide crucial feedback for debugging and user experience.
Choose status codes that accurately reflect what happened - success codes for completed operations, client error codes for invalid requests, and server error codes for unexpected failures.
The key principle is that every handler must return a response. Make those responses meaningful, consistent, and helpful as they represent your API's contract with the world.
Examples & further reading
Examples
- Responses example - Comprehensive example covering complete request-response cycles.
API documentation
- Response class - HTTP response object.
- Body class - Request/response body handling.
- Headers class - Type-safe HTTP headers.
- MimeType class - MIME type handling.
Further reading
- HTTP response status codes - Mozilla documentation on HTTP status codes.
- HTTP headers - Mozilla documentation on HTTP headers.
- MIME types - Mozilla documentation on MIME types.