Skip to main content

Handlers

Handlers are functions that process incoming requests in Relic applications. Think of them as the core logic that decides what to do when someone visits your web server.

What makes Relic handlers special is their flexibility. Unlike traditional web frameworks, where handlers only return HTTP responses, Relic handlers can:

  • Send regular HTTP responses (like web pages or API data).
  • Upgrade connections to WebSockets for real-time communication.
  • Take direct control of the network connection for custom protocols.

Relic handlers take a Request and return a Result, which can be a Response, Hijack (for connection hijacking), or WebSocketUpgrade (for WebSocket connections).

The Handler is the foundational handler type that serves as the core building block for all Relic applications. It provides the most flexibility by accepting any incoming request and allowing you to return various types of results, making it suitable for any kind of request processing logic.

typedef Handler = FutureOr<Result> Function(Request req);
  • Request req is the incoming HTTP request.
  • FutureOr<Result> is a result that can be a Response, Hijack, or WebSocketUpgrade.

Example:

Foundational handler example
Response myHelloWorldHandler(final Request req) {
return Response.ok(body: Body.fromString('Hello from Relic!'));
}

How to define handlers

Synchronous handlers

For simple, fast operations:

Synchronous handler
Response mySyncHandler(final Request req) {
return Response.ok(body: Body.fromString('Fast response'));
}

Asynchronous handlers

For operations that need to wait (database calls, file I/O, etc.):

Asynchronous handler
Future<Response> myAsyncHandler(final Request req) async {
final data = await Future<String>.delayed(
const Duration(milliseconds: 250),
() => 'Hello from Relic!',
);

return Response.ok(body: Body.fromString(data));
}

Using request data

Handlers receive request information including method, URL, headers, and query parameters:

Using request data
Response myContextInfoHandler(final Request req) {
final method = req.method;
final url = req.url;
final userAgent = req.headers.userAgent;

return Response.ok(
body: Body.fromString(
'Method: ${method.name}, Path: ${url.path} User-Agent: ${userAgent ?? 'unknown'}',
),
);
}

Handling WebSocket connections

For real-time bidirectional communication, you can upgrade connections to WebSockets by returning a WebSocketUpgrade:

WebSocket example
WebSocketUpgrade webSocketHandler(final Request req) {
return WebSocketUpgrade((final webSocket) async {
log('WebSocket connection established');

// Send initial greeting to the connected client.
webSocket.sendText('Welcome to Relic WebSocket!');

// Listen for messages and echo them back to the client.
await for (final event in webSocket.events) {
switch (event) {
case TextDataReceived(text: final message):
log('Received: $message');
webSocket.sendText('Echo: $message');
case CloseReceived():
log('WebSocket connection closed');
break;
default:
// Handle binary data, ping/pong, and other WebSocket events.
break;
}
}
});
}

Hijacking connections

For advanced use cases like Server-Sent Events (SSE) or custom protocols, you can hijack the connection:

Connection hijacking example
Hijack mySseHandler(final Request req) {
return Hijack((final channel) async {
// Send Server-Sent Events.
channel.sink.add(utf8.encode('data: Connected'));

// Send periodic updates.
final timer = Timer.periodic(
const Duration(seconds: 1),
(_) => channel.sink.add(utf8.encode('data: ${DateTime.now()}')),
);

// Wait for client disconnect, then cleanup.
await channel.sink.done;
timer.cancel();
});
}

Examples & further reading

Examples

API documentation