LogoRevali

Error Handling

Create custom error responses with ExceptionCatcher

This tutorial builds a domain exception and an ExceptionCatcher that turns it into a consistent JSON error response.

Define a domain exception#

Exceptions are plain Dart classes -- nothing framework-specific:

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: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 exception instance itself is bound automatically by matching the method's exception-typed parameter -- no annotation needed.

Throw it and register the catcher#

routes/controllers/widget_controller.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 /widgets/missing now returns 404 with body {"error": "Widget not found"}, instead of an unhandled-exception 500.

Register @NotFoundCatcher() once at the app or controller level (instead of per-endpoint) to cover every route underneath it -- see Scoping.

What's next?#