LogoRevali

Error Handling

Create custom error responses with ExceptionCatcher

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:

lib/exceptions/not_found_exception.dart
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:

lib/components/not_found_catcher.dart
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#

routes/controllers/widget_controller.dart
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