Skip to content

Code generation

gisila’s routing is written by a build_runner generator. For each @Controller class it emits an attachToApp(GisilaApp app, Router router, {String prefix}) extension method into the file’s .g.dart part. That method registers every route on a shelf_router Router, with the parameter binding, the merged route config and the response rendering spelled out as plain Dart.

Always, for annotated controllers: the annotations do nothing without it. Run it after changing an annotation, a route path, a parameter or a return type.

You can skip the generator for individual routes by writing a ControllerAttacher by hand that registers Shelf handlers (wrapped in gisilaRoute to keep guards and limits). That is useful for a few unusual endpoints, but for ordinary routes the generated code is shorter and harder to get wrong.

Dart has no runtime reflection in AOT-compiled servers, so gisila reads annotations at build time instead:

  1. build_runner runs the gisila builder on every library in your package (it is auto-applied to packages that depend on gisila).
  2. For each @Controller class, the generator reads the class-level guards, @UseRouteConfig and @UseMiddleware, then each route method’s annotation, guards and parameters.
  3. It classifies each parameter (path, query, header, body, form, service, context, request) and emits the code that extracts and converts it.
  4. It emits a constant RouteConfig merge chain, the call to your method and the response conversion.
  5. source_gen’s combining builder writes the result into your_file.g.dart, which your file includes with part 'your_file.g.dart';.

What this buys you:

  • No runtime cost for routing decisions: everything is decided at build time.
  • Readable output. Open the .g.dart file to see exactly how a request reaches your method.
  • Build-time errors for parameters the generator can’t classify.
  • Tooling can reuse it. gisila_doc reads the same annotations to produce OpenAPI specs.

What it costs:

  • A build step. Use watch while developing so output stays in sync.
  • Generated files to manage. Either commit .g.dart files (simple deploys, reviewable diffs) or generate them in CI before building; be consistent.
  • Signature rules to learn: see parameter binding rules.
lib/controllers/health_controller.dart
import 'package:gisila/gisila.dart';
part 'health_controller.g.dart';
@Controller('/health')
class HealthController {
@Get('/')
Future<Map<String, Object?>> check() async => {'status': 'ok'};
}
Terminal window
dart run build_runner build --delete-conflicting-outputs
bin/server.dart
app.registerController(attacher: HealthController().attachToApp);