NestJS / Basic: modules and request contracts / Lesson 6
Request lifecycle, guards, interceptors and filters
Assign each cross-cutting concern to the correct stage and understand which failures bypass later work.
On this page
Lesson goals
- Trace middleware, guards, interceptors, pipes and handler execution.
- Distinguish expected domain results from safe exception translation.
The lifecycle is an ordered contract
Nest request processing has distinct extension points rather than one interchangeable stack. Middleware handles early request work, guards decide admission to a handler, interceptors wrap execution, pipes transform or validate arguments, and the handler invokes the use case. Outbound interceptor work unwinds around the result; exception filters handle uncaught exceptions under their scope. A guard rejection prevents later handler work, but it does not replace object policy deep in a shared service. Read the exact documented scope and order when several global, controller and route components are combined.
Document where each prerequisite is established
The JSON note names a synthetic lifecycle contract without claiming that Nest executed it. Authentication may be performed by a reviewed guard or earlier integration, while authorization needs access to trusted identity and the relevant object. A guard runs before parameter pipes, so it must not assume a transformed DTO already exists simply because the handler annotation names one. Put raw-input inspection behind its own validation or defer the object decision to the service. Interceptors are useful for timing or result transformation, not a substitute for a transaction protocol or a proof of authentication.
{"inbound":["middleware","guards","interceptors-before","pipes","handler"],"outbound":"interceptors-unwind","uncaughtFailure":"applicable-exception-filter","objectPolicy":"shared-domain-service"}
The record explains the selected mental model. It does not encode every scope-order detail or run a Nest request.
Translate failures without hiding uncertainty
A malformed value, denied action, absent object and source outage need deliberate public categories. Framework HTTP exceptions provide transport semantics, while filters adapt safe disclosure and logging. Do not serialize arbitrary exception properties, stack traces or source messages. A filter cannot roll back a committed write, and a timeout response does not prove nonexecution. When using platform-specific response objects inside a filter, preserve the selected adapter boundary. Keep richer diagnostic evidence internal through a controlled correlation identifier rather than returning it to every caller.
Test a prerequisite stop as local logic
The loop model records lifecycle names until a guard denies. It proves only its own synchronous early-stop rule and deliberately omits Nest metadata, asynchronous interceptors and exception filter resolution. The useful invariant is that a denied caller cannot reach the protected handler. A real composed test should assert that invariant through actual guards, pipes and service spies. Do not claim that a local trace validates every global/controller/route ordering rule. Keeping scope honest lets the simple model teach control flow while a separate integration checks the framework’s actual composition.
const trace=[];
for (const stage of ["middleware","guard","pipe","handler"]) {
trace.push(stage); if (stage === "guard") break;
}
console.assert(JSON.stringify(trace) === '["middleware","guard"]');
console.assert(!trace.includes("handler"));
The trace stops at the synthetic guard. It does not instantiate Nest guards or test runtime decorator order.
Keep registrations and tests aligned
A component instantiated manually during global registration may not receive the same dependency injection as a container-managed APP_GUARD or APP_INTERCEPTOR binding. Choose the documented registration form for dependencies and scope. A testing module that invokes a controller directly can bypass the request pipeline entirely, so pair domain tests with focused composed tests. Include rejection, partial source failure and late response commitment where relevant. This course executes the loop and parses the lifecycle record only; no real global registration, filter dispatch or interceptor stream was run. Those limitations belong beside any claim about completed verification.
Common errors and fixes
A guard trusts an already transformed DTO
- Why it happens
- Pipe execution was assumed to precede guards.
- How to fix it
- Validate needed raw values or defer object policy to the service.
- Prevent it next time
- Test the real inbound order.
A filter returns private source details
- Why it happens
- Arbitrary exceptions were exposed as the response contract.
- How to fix it
- Map safe categories and keep internal evidence controlled.
- Prevent it next time
- Assert exact public fields and private-message omission.
Direct controller tests miss a broken guard binding
- Why it happens
- The request pipeline was bypassed.
- How to fix it
- Add a focused composed-route test with the real binding.
- Prevent it next time
- Label direct-call and pipeline evidence separately.
Check your understanding
1. Why can a guard not rely on a parameter pipe’s output?
Show worked solution
Guards execute before parameter pipes in the inbound request flow. The handler’s DTO type does not move transformation earlier. If the guard needs raw input, validate that boundary explicitly, or place the object-specific decision in a shared service that receives already validated values. Verify the actual composition rather than relying on decorator placement in source text.
2. Can an exception filter guarantee that a failed write did not commit?
Show worked solution
No. It translates an observed failure into a response. The source may already have committed before transport or later work failed. Reconcile using the domain operation identity and transaction/replay contract. Keep public error formatting separate from storage atomicity and do not turn a 500 response into an automatic blind retry.
Key takeaways
- Each extension point has a distinct stage and scope.
- Safe failure translation does not establish rollback or pipeline coverage.