CodePark/ questions / Interceptors
START PRACTICINGPRACTICE
Backend question · NestJS › Interceptors

Write an interceptor that logs how long each request takes, and explain how next.handle() works.

@Injectable()
export class TimingInterceptor implements NestInterceptor {
  intercept(ctx: ExecutionContext, next: CallHandler): Observable<unknown> {
    const req = ctx.switchToHttp().getRequest();
    const start = Date.now();
    return next.handle().pipe(
      tap(() => console.log(req.method, req.url, Date.now() - start, "ms")),
    );
  }
}

An interceptor wraps the route handler, so it can run code before and after it. It implements NestInterceptor. Its intercept() method gets two things: the ExecutionContext (details about the current request and handler) and a CallHandler (usually named next).

How next.handle() works:

  • Code before next.handle() runs before the route handler.
  • next.handle() returns an RxJS Observable. This is a stream that will carry the handler's result.
  • The handler only runs when someone subscribes to that observable. Nest subscribes to whatever your intercept() returns.

So you add behaviour by adding RxJS operators to that stream:

  • tap for side effects, like timing logs
  • map to change the response
  • catchError to handle errors
  • timeout for time limits

For the timing logger, save the start time before calling next.handle(). Then use tap to log the elapsed time when the response value comes through.

If you return a stream without calling next.handle(), the handler never runs. Caching interceptors use this to skip the handler and return a cached value.

  • An interceptor implements NestInterceptor.intercept(context, next).
  • Code before next.handle() runs before the handler.
  • next.handle() returns an Observable of the handler's result.
  • RxJS operators such as tap, map and catchError run the after-logic.
  • If you don't call next.handle(), the handler is skipped.