Interceptors: CallHandler, next.handle(), and RxJS Response Pipelines
- Last updated
- Prerequisites:
- NestJS ExecutionContext
- RxJS Observables and Operators (`map`, `tap`, `catchError`)
- nestjs
- interceptors
- rxjs
- callHandler
- aop
- response-mapping
Read at your depth
The practical view
Interceptors implement the `NestInterceptor` interface with `intercept(context, next: CallHandler): Observable<any>`. They use Aspect-Oriented Programming (AOP) to bind extra logic before and after method execution. By calling `next.handle()`, you invoke the route handler and receive an RxJS `Observable` of the response, which you can transform using operators like `map()` (wrap response in `{ data: ... }`), `tap()` (log execution time), or `timeout()`.
The same idea in other frameworks
`return next.handle().pipe(map(data => ({ data })))`.Spring AOP `@Around` advice with `ProceedingJoinPoint.proceed()`.Starlette middleware `response = await call_next(request)`.Express response wrapping middleware overriding `res.json`.Legacy vs modern
Manual Controller Response Wrapping vs Global Transform Interceptor
Manually wrapping responses in `{ data: ... }` pollutes controller logic; an Interceptor normalizes all API responses cleanly.
return { data: users, timestamp: Date.now() }; // ❌ Duplicated across every controllerreturn next.handle().pipe(map(data => ({ data, timestamp: new Date().toISOString() }))); // ✅ NEWInterview gotchas
Context
Interviewers test your knowledge of interceptor pipeline propagation.
The mechanical answer
If you do not call `next.handle()`, the **route handler method will never be executed**. You can use this behavior deliberately to short-circuit requests (e.g. returning cached responses directly from memory via `of(cachedData)` without hitting the controller or database).
Trap
Returning a plain value without calling `next.handle()` or returning an `Observable` will hang the HTTP connection.
Context
Checks knowledge of RxJS operator integration with NestJS interceptors.
The mechanical answer
Pipe the `timeout()` operator onto `next.handle()` and catch `TimeoutError` via `catchError()`: ```typescript return next.handle().pipe( timeout(5000), catchError(err => { if (err instanceof TimeoutError) throw new RequestTimeoutException(); return throwError(() => err); }) ); ```
Trap
Ensure `RequestTimeoutException` from `@nestjs/common` is thrown so NestJS serializes an HTTP 408 response.