Skip to content

Middleware and Errors

Spry keeps request behavior in two explicit places:

  • middleware for cross-cutting flow control
  • error handlers for translating failures into responses

Global middleware

Files in top-level middleware/ are loaded in filename order.

dart
// ignore_for_file: file_names

import 'package:spry/spry.dart';

Future<Response> middleware(Event event, Next next) async {
  final startedAt = DateTime.now();
  final response = await next();
  final duration = DateTime.now().difference(startedAt).inMilliseconds;
  print(
    '${event.method} ${event.url.path} -> ${response.status} (${duration}ms)',
  );
  return response;
}

This is the right place for:

  • request logging
  • tracing
  • auth shells
  • response timing

Scoped middleware

Use _middleware.dart inside routes/ when behavior should apply only to that branch of the route tree.

dart
import 'package:spry/spry.dart';

Future<Response> middleware(Event event, Next next) async {
  event.locals.set(
    #requestId,
    DateTime.now().microsecondsSinceEpoch.toString(),
  );
  return next();
}

This is useful when a subset of routes needs shared locals, auth checks, or response wrapping.

Error handling

Use _error.dart to catch errors inside the current route scope and convert them into a stable response shape.

dart
import 'package:spry/spry.dart';

Response onError(Object error, StackTrace stackTrace, Event event) {
  if (error case NotFoundError()) {
    return Response.json({
      'error': 'not_found',
      'path': event.url.path,
    }, ResponseInit(status: 404));
  }
  if (error case HTTPError()) {
    return error.toResponse();
  }

  return Response.json({
    'error': 'internal_server_error',
    'path': event.url.path,
  }, ResponseInit(status: 500));
}

This is the clean path for:

  • handling NotFoundError
  • returning structured JSON errors
  • avoiding repeated try/catch blocks in handlers

Practical rule

  • if it changes request flow across multiple routes, use middleware
  • if it converts thrown errors into responses, use _error.dart
  • if it belongs to one handler only, keep it inside that handler

For websocket routes, middleware and _error.dart still apply during the handshake phase, but not after the upgrade is committed. Use that phase for auth, validation, and fallback decisions before calling event.ws.upgrade(...). See WebSockets.

Released under the MIT License.