with one click
relic-request-response
// Handle HTTP requests and create responses with Body, headers, and status codes in Relic. Use when reading request data, parsing JSON, building API responses, working with headers, or streaming content.
// Handle HTTP requests and create responses with Body, headers, and status codes in Relic. Use when reading request data, parsing JSON, building API responses, working with headers, or streaming content.
Bootstrap a Relic web server, configure RelicApp, start serving, and enable hot reload. Use when creating a new Relic project, setting up a server, or configuring the development workflow.
Create and apply middleware for auth, CORS, logging, and other cross-cutting concerns. Use context properties for type-safe request-scoped data. Use when adding middleware, request/response transformations, or passing data between middleware and handlers.
Define routes with path parameters, wildcards, tail segments, and typed params in Relic. Use when adding endpoints, handling dynamic URLs, setting up URL patterns, or forwarding requests.
Migrate a Dart web server from Shelf to Relic. Side-by-side reference for every API change. Use when converting Shelf code to Relic, replacing shelf/shelf_router/shelf_web_socket imports, or upgrading an existing server.
Serve static files and directories with caching and cache busting in Relic. Use when serving assets, images, CSS, JS, favicons, or configuring HTTP cache control headers.
Handle WebSocket connections and hijack connections for SSE or custom protocols in Relic. Use when implementing real-time communication, WebSocket endpoints, or server-sent events.
| name | relic-request-response |
| description | Handle HTTP requests and create responses with Body, headers, and status codes in Relic. Use when reading request data, parsing JSON, building API responses, working with headers, or streaming content. |
Every handler receives a Request with these key properties:
method -- Method enum (Method.get, Method.post, etc.)url -- full original URIheaders -- type-safe header accessbody -- Body wrapper (single-read stream)pathParameters -- route parameters set by routingqueryParameters -- query string parametersRequest.headers is immutable. Request.body is mutable (middleware may replace it), but the underlying stream can only be read once.
app.get('/search', (req) {
final query = req.queryParameters.raw['query'];
final page = req.queryParameters.raw['page'];
return Response.ok(body: Body.fromString('Search: $query, page: $page'));
});
const pageParam = IntQueryParam('page');
const limitParam = IntQueryParam('limit');
const priceParam = DoubleQueryParam('price');
app.get('/products', (req) {
final page = req.queryParameters.get(pageParam); // int (throws if missing)
final limit = req.queryParameters.get(limitParam); // int
final maxPrice = req.queryParameters.get(priceParam); // double
return Response.ok(body: Body.fromString('page=$page limit=$limit price=$maxPrice'));
});
Nullable variant:
final page = req.queryParameters(pageParam); // int? -- null if missing
Built-in: IntQueryParam, DoubleQueryParam, NumQueryParam. Custom:
const sortParam = QueryParam<SortOrder>('sort', SortOrder.parse);
const fromParam = QueryParam<DateTime>('from', DateTime.parse);
Reusable specialization:
final class DateTimeQueryParam extends QueryParam<DateTime> {
const DateTimeQueryParam(String key) : super(key, DateTime.parse);
}
app.get('/filter', (req) {
final tags = req.url.queryParametersAll['tag'] ?? []; // List<String>
return Response.ok(body: Body.fromString('Tags: $tags'));
});
// GET /filter?tag=dart&tag=server → Tags: [dart, server]
app.get('/info', (req) {
final userAgent = req.headers.userAgent; // String?
final contentLength = req.headers.contentLength; // int?
final mimeType = req.mimeType; // MimeType?
// ...
});
app.get('/protected', (req) {
final auth = req.headers.authorization;
if (auth is BearerAuthorizationHeader) {
final token = auth.token;
return Response.ok(body: Body.fromString('Token: $token'));
} else if (auth is BasicAuthorizationHeader) {
final username = auth.username;
return Response.ok(body: Body.fromString('User: $username'));
}
return Response.unauthorized();
});
The body can only be read once. A second read throws StateError.
app.post('/submit', (req) async {
final text = await req.readAsString();
return Response.ok(body: Body.fromString('Received: $text'));
});
app.post('/api/users', (req) async {
try {
final body = await req.readAsString();
final data = jsonDecode(body) 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,
),
);
}
return Response.ok(
body: Body.fromString(
jsonEncode({'message': 'User created', 'name': name}),
mimeType: MimeType.json,
),
);
} catch (e) {
return Response.badRequest(
body: Body.fromString(jsonEncode({'error': 'Invalid JSON: $e'}), mimeType: MimeType.json),
);
}
});
app.post('/upload', (req) async {
if (req.isEmpty) {
return Response.badRequest(body: Body.fromString('Body required'));
}
final stream = req.read(); // Stream<Uint8List>
int totalBytes = 0;
await for (final chunk in stream) {
totalBytes += chunk.length;
}
return Response.ok(body: Body.fromString('Received $totalBytes bytes'));
});
const maxFileSize = 10 * 1024 * 1024; // 10 MB
final sink = file.openWrite();
try {
await sink.addStream(req.read(maxLength: maxFileSize));
} on MaxBodySizeExceeded {
return Response.badRequest(body: Body.fromString('File too large'));
} finally {
await sink.close();
}
Response.ok(body: Body.fromString('Success')) // 200
Response.noContent() // 204
Response.badRequest(body: Body.fromString('Bad input')) // 400
Response.unauthorized() // 401
Response.notFound() // 404
Response.internalServerError() // 500
Response(418, body: Body.fromString('I am a teapot')) // custom
// Text (auto-detects MIME: JSON, HTML, XML, or plain text)
Body.fromString('Hello') // text/plain
Body.fromString('{"key": "value"}') // application/json
Body.fromString('<!DOCTYPE html><html>...</html>') // text/html
// Explicit MIME type
Body.fromString('<html>...</html>', mimeType: MimeType.html)
Body.fromString(jsonEncode(data), mimeType: MimeType.json)
// Binary (auto-detects PNG, JPEG, PDF, etc.)
Body.fromData(imageBytes) // image/png, etc.
Body.fromData(data, mimeType: MimeType.octetStream) // explicit
// Streaming (for large payloads)
Body.fromDataStream(fileStream, contentLength: fileSize) // known size
Body.fromDataStream(dynamicStream) // chunked encoding
// Empty
Body.empty()
Encoding defaults to UTF-8. Override with:
Body.fromString('Café', mimeType: MimeType.plainText, encoding: latin1)
app.get('/api/data', (req) {
final headers = Headers.build((h) {
h.cacheControl = CacheControlHeader(maxAge: 3600, publicCache: true);
h['X-Custom-Header'] = ['value'];
});
return Response.ok(
headers: headers,
body: Body.fromString(jsonEncode({'status': 'ok'}), mimeType: MimeType.json),
);
});
app.get('/page', (req) {
final html = '<!DOCTYPE html><html><body><h1>Welcome</h1></body></html>';
return Response.ok(body: Body.fromString(html, mimeType: MimeType.html));
});
app.get('/stream', (req) async {
Stream<Uint8List> generate() async* {
for (var i = 0; i < 100; i++) {
await Future<void>.delayed(Duration(milliseconds: 50));
yield utf8.encode('{"item": $i}\n');
}
}
return Response.ok(
body: Body.fromDataStream(generate(), mimeType: MimeType.json),
);
});
Request is immutable. Use copyWith to create a modified copy:
final rewritten = request.copyWith(
url: request.url.replace(path: '/new-path'),
);
final modified = request.copyWith(
url: request.url.replace(path: '/other'),
headers: Headers.build((h) => h['X-Custom'] = ['value']),
);