Combine
Spry provides first-party helpers for composing middleware without changing the core middleware contract.
Import them from:
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 chainexcept(...): skip one middleware when a request matches a conditionsome(...): try several candidate middleware in order until one succeeds
every(...)
Use every(...) when you want to bundle several middleware into one reusable chain.
Middleware every(Iterable<Middleware> middlewares)every([a, b, c]) behaves like:
a(event, () => b(event, () => c(event, next)))Basic usage
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 tonext().- 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.
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
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.
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
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 downstreamnextis 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 errorSomeErrorThrower.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:
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.