Skip to main content

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:

  1. Status Code - A three-digit number indicating the outcome (e.g., 200 for success, 404 for not found)
  2. Headers - Metadata about the response (content type, caching rules, cookies, etc.)
  3. 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:

200 OK text response
  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:

400 Bad Request JSON error
  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:

Custom status code
  app.get('/teapot', (final req) {
return Response(
418, // I'm a teapot
body: Body.fromString('I refuse to brew coffee'),
);
});
tip

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():

Text response
  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:

HTML response
  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:

JSON response
  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():

Binary response
  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:

Streaming response
  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:

Empty responses
  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:

Set response 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,
),
);
});
tip

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

Further reading