Middleware combinators
DaloyJS exposes three composition primitives for Hooks bundles. Use every() to package a full stack, some() to accept any one valid proof, and except() to skip a gate for specific paths.
every(): run a whole stack in order
every(...layers) merges several Hooks bundles into one that runs each layer in registration order. It is equivalent to calling app.use(...) for each bundle, but lets you name and reuse a curated stack. All lifecycle phases compose: onRequest and onResponse run in order, beforeHandle and onError stop on the first Response, and afterHandle plus onSend thread the value through each layer.
- 01layer 1requestId()tag the request
- 02layer 2bearerAuth()verify the token
- 03layer 3rateLimit()throttle the shared admin bucket
- 04resultHandlerone reusable adminStack value
some(): accept any one proof of identity
some(...layers) runs each bundle's beforeHandle in order and accepts the request as soon as one bundle passes without throwing or returning a Response. Use it for routes that accept more than one credential style, such as a bearer token or a signed session cookie.
Semantics worth knowing:
- The first bundle that resolves without throwing or returning a
Responsewins. Its context mutations, including headers andctx.state, are preserved. - A bundle that returns a
Responseis treated as a denial, and the next bundle gets a chance. If every bundle returns a denial, the firstResponseis sent. - When every bundle fails and the first denial was a thrown error, that error is rethrown. Put the auth method whose status and
WWW-Authenticatechallenge you want clients to see first. - Only the
beforeHandleselection strategy changes.afterHandle,onSend,onResponse, andonErrorfrom every bundle still compose normally.
except(): apply everywhere but a few paths
except(when, hooks) runs a bundle on every request except those matching when. The canonical use is applying auth everywhere except health checks, OpenAPI JSON, and docs assets.
The when matcher accepts:
- A path string starting with
/.*matches one path segment with no slash, and**matches any suffix. - An array of path patterns.
- A predicate function that receives the request context and returns
trueto skip the gated bundle.
Path matching uses the same new URL(request.url).pathname view the router sees. It is case-sensitive, exact for non-wildcard patterns, and does not add a rewrite or extra decode step. That makes exemptions fail closed for case changes, trailing-slash mismatches, and encoded traversal tricks.
Only the wrapped bundle's beforeHandle phase is skipped. Its onRequest, afterHandle, onSend, and onResponse phases still run. Wrap each bundle with except() individually if you need to gate different phases with different rules.
Composing the three
The primitives nest. A common production shape is to run correlation, secure headers, and authentication everywhere, but skip the auth gate for health and documentation endpoints.