| name | relic-routing |
| description | 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. |
Relic Routing
Routes map incoming requests to handlers based on path and HTTP method. Register routes on RelicApp (which implements RelicRouter).
Handler signature
typedef Handler = FutureOr<Result> Function(Request req);
A handler returns a Result: either a Response, WebSocketUpgrade, or Hijack.
Registering routes
final app = RelicApp()
..get('/', (req) => Response.ok(body: Body.fromString('Hello World!')))
..post('/', (req) => Response.ok(body: Body.fromString('Got POST')))
..put('/user', (req) => Response.ok(body: Body.fromString('PUT /user')))
..delete('/user', (req) => Response.ok(body: Body.fromString('DELETE /user')));
Core add method
The convenience methods call add() internally:
app.add(Method.patch, '/api', (req) {
return Response.ok(body: Body.fromString('PATCH /api'));
});
Multiple methods
app.anyOf({Method.get, Method.post}, '/admin', (req) {
return Response.ok(body: Body.fromString('Admin - ${req.method.name}'));
});
app.any('/wildcard', handler); // all HTTP methods
Path parameters
Use :name to capture a segment. Access with Symbol-based key:
app.get('/users/:id', (Request req) {
final userId = req.pathParameters.raw[#id];
return Response.ok(body: Body.fromString('User $userId'));
});
Typed path parameters
Define typed accessors for automatic parsing:
const idParam = IntPathParam(#id);
const latParam = DoublePathParam(#lat);
const lonParam = DoublePathParam(#lon);
app.get('/users/:id', (Request req) {
final userId = req.pathParameters.get(idParam); // int
return Response.ok(body: Body.fromString('User $userId'));
});
app.get('/location/:lat/:lon', (Request req) {
final lat = req.pathParameters.get(latParam); // double
final lon = req.pathParameters.get(lonParam); // double
return Response.ok(body: Body.fromString('Location: $lat, $lon'));
});
Nullable variant (returns null if missing):
final userId = req.pathParameters(idParam); // int?
Built-in accessors: IntPathParam, DoublePathParam, NumPathParam.
Custom path parameter type
const statusParam = PathParam<Status>(#status, Status.parse);
const dateParam = PathParam<DateTime>(#date, DateTime.parse);
Reusable specialization:
final class DateTimePathParam extends PathParam<DateTime> {
const DateTimePathParam(Symbol key) : super(key, DateTime.parse);
}
const dateParam = DateTimePathParam(#date);
Wildcards
* matches any single segment without capturing:
app.get('/files/*/download', (req) {
return Response.ok(body: Body.fromString('Downloading...'));
});
Tail segments
/** captures the entire remaining path, exposed via request.remainingPath:
app.get('/static/**', (req) {
final path = req.remainingPath.toString();
return Response.ok(body: Body.fromString('Serve $path'));
});
Required when serving directories so the handler knows which file was requested.
Route priority
- Literal segments take priority over dynamic segments.
- Backtracking ensures the best match if a literal leads to a dead end.
- Registration order does not affect matching.
app.get('/:entity/:id', entityHandler);
app.get('/users/:id/profile', profileHandler);
Request for /users/789: tries literal users path first (Route 2), needs third segment /profile but path ends -- backtracks to :entity parameter (Route 1) with entity=users, id=789.
Tail segments with specific routes:
app.get('/files/**', catchAllHandler);
app.get('/files/special/report', reportHandler);
/files/special/report matches the exact route; /files/special/other falls back to catch-all.
Request forwarding
Re-route a request through the same router with copyWith + forwardTo:
app.get('/v1/users', (req) {
final newReq = req.copyWith(url: req.url.replace(path: '/v2/users'));
return req.forwardTo(newReq);
});
app.get('/v2/users', (req) {
return Response.ok(body: Body.fromString('Users list'));
});
forwardTo sends the new request through the full routing pipeline including middleware. It throws StateError if the request was not routed through a RelicRouter.