Routing
Routes in Finch are defined using FinchRoute objects collected inside a routing function. The routing function is registered with app.addRouting() and is called for every incoming request.
Routing Function
import 'package:finch/finch_route.dart';
import '../controllers/home_controller.dart';
final homeController = HomeController();
Future<List<FinchRoute>> getWebRoute() async {
return [
FinchRoute(
key: 'root',
path: '/',
methods: Methods.ONLY_GET,
index: homeController.index,
),
];
}
Register it in app.dart:
app.addRouting(getWebRoute);
FinchRoute Parameters
| Parameter | Type | Description |
|---|---|---|
key |
String |
Unique route identifier. Used to generate URLs ($e.routeUrl('key')) |
path |
String |
URL path segment. Supports {param} or :param and wildcard * |
methods |
List<String> |
Allowed HTTP methods. Use Methods constants |
index |
Future<String> Function()? |
Handler function (no rq parameter; use Context.rq or a controller method) |
controller |
Controller? |
Controller instance. Its index() is called when no index is set |
children |
List<FinchRoute> |
Nested child routes. Path is relative to parent |
extraPath |
List<String> |
Additional paths that map to the same route |
auth |
AuthController? |
Authentication guard for this route |
permissions |
List<String> |
Permission strings checked after auth |
middlewares |
List<Middleware> |
Middleware chain executed before the handler |
hosts |
List<String> |
Restrict to specific hostnames (['*'] = all) |
ports |
List<int> |
Restrict to specific ports ([] = all) |
params |
Map<String, dynamic> |
Default template parameters |
excludePaths |
List<String> |
Sub-paths excluded from this route |
apiDoc |
Future<ApiDoc>? Function()? |
API documentation for Swagger |
widget |
String |
Path to a template file to render directly, with no controller or index needed (see Widget Routes below) |
title |
String |
Page title, available in templates as {{ $e.pageTitle }} |
Use only one of widget, index, or controller per route. If more than one is set, widget is checked first, then index, then controller.index() as a fallback.
HTTP Methods
// Convenience constants from Methods class:
Methods.ONLY_GET // ['GET']
Methods.ONLY_POST // ['POST']
Methods.ONLY_PUT // ['PUT']
Methods.ONLY_DELETE // ['DELETE']
Methods.GET_POST // ['POST', 'GET']
Methods.ALL // All standard HTTP methods
// Or a custom list:
methods: [Methods.GET, Methods.POST, Methods.DELETE]
Path Parameters
Use {name} syntax to capture URL segments. Read them in the controller using rq.getParam('name'):
FinchRoute(
key: 'users.show',
path: 'users/{id}', // or 'users/:id'
methods: Methods.ONLY_GET,
index: userController.show,
),
Note: Dynamic path segments can be defined using either {param} or :param syntax. Both are supported and function identically.
// In UserController:
Future<String> show() async {
var id = rq.getParam('id');
return rq.renderData(data: {'id': id});
}
Widget Routes
When a route only needs to render a template with no custom logic, set widget to a template path instead of writing a controller:
FinchRoute(
key: 'about',
path: 'about',
title: 'About Us',
widget: 'pages/about',
),
widget is checked before index and controller, and — unlike those two — it does not wait for this route's own auth to run first; only permissions inherited from an already-authenticated parent route are checked. If a widget route needs authentication of its own, attach auth to a parent route instead of relying on the auth field of the widget route itself, or use index/controller so the auth check runs normally.
Wildcard Paths and Exclusions
A path ending in * matches the route itself and every sub-path beneath it — useful for catch-all handlers such as a single-page app fallback or a generic file proxy:
FinchRoute(
key: 'spa.fallback',
path: 'app/*',
index: homeController.spaIndex,
excludePaths: ['app/api'], // still falls through to other routes for this sub-path
),
excludePaths lists sub-paths that should not be swallowed by the wildcard, so more specific routes (or static files) underneath the wildcard can still be reached.
Nested Routes (children)
Child route paths are relative to the parent path:
FinchRoute(
key: 'example',
path: 'example',
index: homeController.redirectToRoot,
children: [
FinchRoute(
key: 'example.form.get',
path: 'form',
methods: Methods.ONLY_GET,
index: homeController.exampleForm,
),
FinchRoute(
key: 'example.form.post',
path: 'form',
methods: Methods.ONLY_POST,
index: authController.loginPost,
),
FinchRoute(
key: 'example.panel',
path: 'panel',
methods: Methods.ALL,
auth: authController,
permissions: ['admin'],
index: homeController.exampleAuth,
),
],
),
Extra Paths
extraPath maps additional URL prefixes to the same route and its children:
FinchRoute(
key: 'root.mysql',
path: 'example/mysql',
extraPath: ['api/example/mysql'],
methods: Methods.GET_POST,
index: homeController.exampleMysql,
),
Creating Multiple Similar Routes
FinchRoute.makeList creates several routes that share the same configuration but differ only by path — handy for aliases or near-identical endpoints:
var routes = FinchRoute.makeList(
paths: ['users', 'members', 'people'],
methods: Methods.ONLY_GET,
controller: UserController(),
auth: AppAuthController(),
permissions: ['user.read'],
key: 'user.alias', // becomes 'user.alias.1', 'user.alias.2', 'user.alias.3'
);
Middleware
Attach one or more Middleware instances to a route (or a fluent .middleware() chain) to run request-filtering logic before the handler executes — logging, header checks, rate limiting, CORS, and similar cross-cutting concerns. See the Middleware Guide for the full API and execution order relative to auth and permissions.
Host and Port Filtering
Restrict a route to specific hosts or ports:
FinchRoute(
key: 'root.localhost',
path: 'example/host',
hosts: ['localhost'],
ports: [80, 8085],
index: homeController.renderLocalhost,
methods: Methods.ALL,
),
FinchRoute(
key: 'root.host',
path: 'example/host',
ports: [80, 8085],
hosts: ['127.0.0.1'],
index: homeController.render127001,
methods: Methods.ALL,
),
Authentication Guard
Attach an AuthController to protect a route. If auth() returns false, the request is rejected before reaching the handler:
FinchRoute(
key: 'admin.panel',
path: 'admin/panel',
auth: AppAuthController(),
permissions: ['admin'],
index: adminController.panel,
),
See Auth Controller for implementation details.
Route Caching
Use the .cache() extension to cache responses:
FinchRoute(
key: 'root.route',
path: 'example/route',
methods: Methods.ONLY_GET,
index: homeController.exampleRoute,
).cache(
cacheDuration: Duration(minutes: 10),
cacheType: [CacheParam.path, CacheParam.method, CacheParam.language],
cacheSource: CacheSource.file,
),
See Route Cache for full details.
Generating URLs from Route Keys
In templates, use $e.routeUrl('key') to generate URLs:
<a href="{{ $e.routeUrl('example.panel') }}">Panel</a>
<a href="{{ $e.routeUrl('users.show', {'id': user.id}) }}">View User</a>
Get All Routes
Retrieve the full route list at runtime:
var routes = await app.getAllRoutes();
How Matching Works
For every incoming request, Finch walks the routing list you registered with app.addRouting() in order, top to bottom:
- Host/port filter — a route is skipped immediately if
hostsorportsdon't match the request. - Path match — the route's
path(plus eachextraPath) is compared against the request path, resolving{param}/:paramplaceholders and wildcard*suffixes along the way. - If the path matches but
methodsdoesn't include the request's HTTP method, the route is treated as not found and matching continues to the next route. - If the route has
children, Finch recurses into them once the parent's path prefix matches —authandmiddlewareson the parent still run first and apply to every child. - The first route that fully matches (path, method, host, port, auth, permissions, middleware) handles the request; no further routes are tried.
- If nothing matches, Finch falls back to serving a static file from
publicDir, and returns a404if that file doesn't exist either.
Because matching is first-match-wins, order matters: put more specific routes before broader wildcard routes that could otherwise swallow them.