In a web framework, what changes when a hook is attached globally rather than to one route or group?
answer
- scope decides eligibility, not behaviour
- every request versus a matched subset
- group scope covers routes added later
- opt-out global versus opt-in per-route
- unmatched paths reach only pre-routing hooks
basics
~20 sScope decides which requests enter the hook. A globally attached hook runs for every request the server accepts, including ones that match no route; a route- or group-scoped hook runs only for requests whose matched route sits inside that scope.
solid answer
~40 sAttachment scope answers one question: for which requests does the framework splice this hook into the chain? A **global** registration puts it in front of everything the server accepts, so it also sees requests that end as `404` or `405` and any future route anyone adds. A **group or prefix** registration puts it in front of the subtree registered under that group, so routes added to that group later inherit it automatically, and routes outside it never run it. A **per-route** registration puts it in front of exactly one handler. The practical difference is not typing effort but the *default*: a global hook is opt-out, a per-route hook is opt-in, and security-relevant hooks want to be opt-out so a forgotten registration cannot silently expose an endpoint.
go deeper
Remember the three scopes and one sentence each: global means every request, group means a subtree of routes, per-route means one handler. Say which requests reach each.
Explain the mechanics: a group composes its chain around the routes registered inside it, so later additions inherit it, while a per-route chain wraps exactly one handler and nothing else.
Argue from failure direction. A forgotten global hook wrongly protects; a forgotten per-route hook wrongly exposes. Choose the scope whose accident you can live with, and test the boundary.
Treat scope as the unit of policy. The question to answer for a whole codebase is how someone determines which hooks run for an endpoint without reading every hook's body.
A hook — the words *middleware*, *filter* and *interceptor* all name the same idea in different framework families — is code that wraps request handling: it can inspect or change the request on the way in, delegate to the rest of the chain, and inspect or change the response on the way out. **Scope** is a separate axis from what the hook does. Scope answers only this: *for which requests does the framework splice this hook into the chain at all?* ## The three attachment scopes - **Global (application-wide).** Registered against the server or the application object rather than against any route. Every request the server accepts passes through it, whatever it is addressed to. - **Group, prefix or subtree.** Registered against a named collection of routes — a group object, a mounted sub-router, or everything under a path prefix. It runs for a request only if the route that matched belongs to that collection. - **Per-route.** Registered in the declaration of one route, alongside its handler. It runs only when that one route is the match. Most frameworks offer at least the global and the per-route form; the middle form is the one whose shape varies most, and in some frameworks the group is a real object you register routes into, while in others it is a path-pattern filter applied at request time. ## What scope changes | Question | Global | Group / prefix | Per-route | |---|---|---|---| | Runs for a request matching **no** route? | Yes, if attached before routing | No | No | | Runs for a route added **later** by someone else? | Yes | Yes, if added inside the group | No | | Default if a developer forgets it | Protected (opt-out) | Protected inside the group | Unprotected (opt-in) | | Visible in the route declaration | No | Only at the group declaration | Yes, next to the handler | | Cost when it should not apply | Paid on every request | Paid inside the subtree | Not paid | ## The default is the real difference The two forms are not interchangeable ways of saying the same thing, because they fail in opposite directions. With a global registration, adding a new route inherits the hook: the failure mode is a hook running where it was not wanted — a wasted lookup, an unwanted header, a body read that a streaming endpoint did not want. With a per-route registration, adding a new route inherits nothing: the failure mode is a hook *not* running where it was needed, and if that hook was authentication, the new endpoint is simply open. That asymmetry is why the usual advice is to attach anything that enforces a policy at the widest scope that policy covers, and to carve out the exceptions explicitly, rather than to opt each route in one at a time. Conversely, anything that is genuinely specific to one handler — parsing one endpoint's unusual body format, a cache policy that only one resource has — belongs on that route, where a reader of the route sees it. ## Scoping by checking the path inside a global hook A third option exists and is worth naming because candidates often propose it: register one global hook and have it inspect the request path itself, doing nothing when the path does not interest it. This is sometimes the right tool, but it is not equivalent to real scoping: 1. It usually runs **before routing**, so it compares a raw request path string, not the route that the router would have chosen. Trailing slashes, percent-encoding, case and duplicate separators are its problem now. 2. The scope becomes invisible: nothing in the route declarations says that this hook applies here and not there, so the only way to answer "what runs for this endpoint?" is to read the hook's body. 3. It still pays the cost of being invoked on every request, even if that cost is only a comparison. ## Choosing in practice - Put **policy** hooks — authentication, request-size limits, correlation identifiers — at the widest scope the policy covers. - Put **subtree** concerns on the group: everything under an administrative or internal subtree, for instance, shares a hook that nothing else should get. - Put **handler-specific** behaviour on the route, where the next reader of that route will see it. - Prefer moving a route into a differently-scoped group over adding a condition inside a hook: the group is declarative and greppable, the condition is not.
- If a hook is attached to a group, does a route added to that group a year later run it?Yes, when the group is a real registration scope: routes are registered into the group, and the framework composes the group's chain around whatever it contains at startup. That inheritance is the point of group scoping — it is why a subtree keeps its policy without anyone remembering to repeat it.
- When is per-route attachment the better choice even for a cross-cutting concern?When the concern genuinely belongs to one handler and would be wrong elsewhere: an endpoint that streams a large body and must not have it buffered, or one resource whose caching headers differ from everything around it. Attaching it on the route keeps it visible to whoever reads that route.
- Does a globally attached hook cost anything for endpoints that do not need it?It costs at least an invocation per request, and more if it does work before deciding it is uninterested — reading a header, parsing a body, or touching a store. That is a real argument for narrower scopes, but it is an efficiency argument, and it should not override a security default.
A global hook is the turnstile at the building entrance; a group hook is the badge reader on one floor; a per-route hook is the lock on a single office door. Forgetting the office lock leaves one room open, and forgetting the turnstile leaves the building open.
saying these in an interview costs you the question
- Treats global and per-route attachment as only a difference in typing effort
- Says a route-scoped hook also runs for requests that match no route
- Thinks a group-scoped hook misses routes added to that group later
- Assumes a path check inside a global hook is equivalent to real route scoping
- Opts each route into authentication individually and calls that explicit