Skip to content

Combine

Spry provides first-party helpers for composing middleware without changing the core middleware contract.

Import them from:

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

Which one should you use?

Use the helper that matches the way you want requests to flow:

  • every(...): run several middleware as one ordered middleware chain
  • except(...): skip one middleware when a request matches a condition
  • some(...): try several candidate middleware in order until one succeeds

every(...)

Use every(...) when you want to bundle several middleware into one reusable chain.

dart
Middleware every(Iterable<Middleware> middlewares)

every([a, b, c]) behaves like:

dart
a(event, () => b(event, () => c(event, next)))

Basic usage

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

final middleware = every([
  requestId(),
  timing(),
]);

Behavior

  • Middleware runs in the provided order.
  • every([]) falls through to next().
  • If one middleware returns a Response, later middleware is not run.
  • Errors continue through Spry's normal error pipeline.

Why this exists

every(...) is useful when you want one route binding to apply a small stack of middleware without creating a wrapper file whose only job is to forward to other middleware.

except(...)

Use except(...) when one middleware should apply almost everywhere, but not on a small set of routes.

dart
Middleware except(
  Middleware middleware,
  bool Function(Event event) when,
)

When when(event) returns true, Spry skips the wrapped middleware and continues to next(). Otherwise, Spry runs the wrapped middleware normally.

Basic usage

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

final middleware = except(
  timing(),
  (event) => event.pathname == '/healthz',
);

This is useful for excluding middleware from health checks, internal probes, or other routes where the wrapped behavior should not apply.

Behavior

  • Matching requests skip the wrapped middleware and continue to next().
  • Non-matching requests run the wrapped middleware normally.
  • Errors continue through Spry's normal error pipeline.

some(...)

Use some(...) when you have several fallback candidates and any one of them succeeding is enough.

dart
Middleware some(
  Iterable<Middleware> middlewares, {
  SomeErrorThrower Function()? createThrower,
})

some(...) is different from every(...): it is not trying to run everything. It is trying candidates one by one until one completes successfully.

Basic usage

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

final jwtMiddleware = (event, next) async {
  throw Exception('JWT auth failed');
};

final sessionMiddleware = (event, next) async {
  return next();
};

final middleware = some([jwtMiddleware, sessionMiddleware]);

This is useful for fallback-style middleware such as trying multiple authentication strategies in order until one works.

Behavior

  • Middleware runs in the provided order.
  • some([]) throws during middleware construction.
  • Returning normally counts as success, including returning the result of next().
  • Throwing counts as failure and moves on to the next candidate.
  • By default, if every candidate fails, some(...) throws the first tracked error.
  • All candidates share the same wrapped next(), so downstream next is still only executed once.

SomeErrorThrower

Most users do not need to think about SomeErrorThrower. It only matters when every some(...) candidate fails.

SomeErrorThrower decides which tracked failure some(...) should finally throw:

  • SomeErrorThrower.first(): throw the first tracked error
  • SomeErrorThrower.last(): throw the last tracked error

If you want some(...) to prefer the last failure instead of the default first failure, pass a custom strategy:

dart
final middleware = some(
  [jwtMiddleware, sessionMiddleware],
  createThrower: SomeErrorThrower.last,
);

SomeErrorThrower is an open interface. Spry ships first() and last() as built-in defaults, but you can implement your own thrower if you need custom failure selection behavior.

When to use it

Use some(...) only when throwing is an acceptable way for a candidate to signal failure.

This is a good fit for fallback middleware such as:

  • multiple auth strategies
  • alternate request resolvers
  • optional guards with ordered fallback

It is usually a poor fit for middleware whose errors should always surface immediately, such as logging, timing, or other request diagnostics.

Released under the MIT License.