In this tutorial you turn one of your own exceptions into a consistent JSON error response with an exception catcher. If you only need a status and an error code, throwing
HttpError is simpler and needs no catcher.
You don't need a catcher for missing or invalid bindings: Revali already turns MissingArgumentException
into a 400.
Define a domain exception#
An exception is a plain Dart class:
class NotFoundException implements Exception {
const NotFoundException(this.message);
final String message;
}
Catch it and shape the response#
A LifecycleComponent method that returns ExceptionCatcherResult<T> catches every exception of type
T thrown anywhere in the request lifecycle:
import 'package:my_app/exceptions/not_found_exception.dart';
import 'package:revali_router/revali_router.dart';
class NotFoundCatcher implements LifecycleComponent {
const NotFoundCatcher();
ExceptionCatcherResult<NotFoundException> catchNotFound(
NotFoundException exception,
) {
return ExceptionCatcherResult.handled(
statusCode: 404,
body: {'error': exception.message},
);
}
}
The parameter typed as the exception receives the thrown exception automatically, with no annotation needed.
Throw it and register the catcher#
import 'package:my_app/components/not_found_catcher.dart';
import 'package:my_app/exceptions/not_found_exception.dart';
import 'package:revali_router/revali_router.dart';
@Controller('widgets')
class WidgetController {
const WidgetController();
@NotFoundCatcher()
@Get('missing')
String missing() {
throw const NotFoundException('Widget not found');
}
}
GET /api/widgets/missing now returns 404 with the body {"error": "Widget not found"}
instead of a 500. A catcher's body is sent as written: it isn't wrapped in data.
Register @NotFoundCatcher() once at the app or controller level (instead of on each endpoint) to cover every route under it. See
Scoping.
Next: Middleware and Guards · Exception Catchers reference · Error Responses