LogoRevali

Controllers

Group related endpoints under one URL path with a @Controller class

A controller is a class annotated with @Controller(path) whose annotated methods become HTTP endpoints. Use one controller per resource (users, orders, ...); every endpoint in it is served under the controller's path.

Minimal example#

routes/controllers/users_controller.dart
import 'package:revali_router/revali_router.dart';

@Controller('users')
class UsersController {
  const UsersController();

  @Get()
  List<String> list() => ['alice', 'bob'];

  @Get(':id')
  String get(@Param() String id) => 'user $id';
}
curl http://localhost:8080/api/users
# {"data":["alice","bob"]}

curl http://localhost:8080/api/users/42
# {"data":"user 42"}

To scaffold one instead, run dart run revali create controller (see CLI).

Rules#

RuleDetail
LocationThe file must be under routes/.
File name Must end in _controller.dart or .controller.dart, or the file is ignored.
AnnotationExactly one @Controller per class.
URL /<app prefix>/<controller path>/<method path> . The app prefix defaults to api .
Method name Not part of the URL. @Get() with no path binds to the controller path itself.
Duplicates Two endpoints with the same HTTP method and path in one controller fail the build.

How the pieces combine:

ControllerMethod annotationRoute
@Controller('users') @Get() GET /api/users
@Controller('users') @Get(':id') GET /api/users/:id
@Controller('users') @Post() POST /api/users
@Controller('shops/:shopId') @Get('orders') GET /api/shops/:shopId/orders

Path syntax (:param, leading slashes) is covered in HTTP Methods.

Constructor dependencies#

Constructor parameters are resolved from dependency injection. @Dep() is optional on controller constructor parameters.

routes/controllers/users_controller.dart
import 'package:revali_router/revali_router.dart';

@Controller('users')
class UsersController {
  const UsersController(this._users);

  final UserService _users;

  @Get()
  Future<List<User>> list() => _users.all();
}
  • Revali uses the first constructor declared in the class. The class must have at least one public constructor; declare it first.
  • A controller never receives the request directly. Read request data with binding annotations.

Instance lifetime#

type:Behavior
InstanceType.singleton (default) One instance, created once and reused for every request.
InstanceType.factoryA new instance for every request.
@Controller('users', type: InstanceType.factory)
class UsersController {
  const UsersController();
}

Sharing endpoints between controllers#

Annotated methods on a superclass or mixin become routes on the controller that inherits them:

abstract class CrudBase {
  const CrudBase();

  @Get('all')
  List<String> findAll() => const [];
}

mixin HealthEndpoints {
  @Get('health')
  String health() => 'ok';
}

@Controller('items')
class ItemsController extends CrudBase with HealthEndpoints {
  const ItemsController();

  @Post()
  String create() => 'created';
}

ItemsController serves POST /api/items, GET /api/items/all and GET /api/items/health.

  • Override with a method annotation and yours replaces the inherited route.
  • Override without one and the inherited route stays, dispatching to your implementation.

Next: HTTP Methods · Binding