Interceptors and Middleware

Nest keeps both. Middleware is Express 24,430 middleware — (req, res, next), running before the router, with no idea which handler will be chosen. Interceptors wrap the handler itself: they see the execution context, run code before and after, and can transform what the handler returned, because they work on an RxJS 31,705 stream rather than a response object. Bookshelf's RequestIdMiddleware is four lines: read x-request-id or mint randomUUID().slice(0, 8), set req.requestId, echo X-Request-Id, next().

src/common/logging.interceptor.ts — one timed line per requestJavaScript
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  private readonly logger = new Logger('HTTP');
  intercept(ctx: ExecutionContext, next: CallHandler): Observable<unknown> {
    const [req, res] = [ctx.switchToHttp().getRequest(), ctx.switchToHttp().getResponse()];
    const t0 = performance.now();
    const done = (code: number, at: 'log' | 'warn') =>
      this.logger[at](`${req.method} ${req.originalUrl} ${code} ` +
        `${(performance.now() - t0).toFixed(1)}ms id=${req.requestId}`);
    return next.handle().pipe(tap({ next: () => done(res.statusCode, 'log'),
      error: (e) => done(e.status ?? 500, 'warn') })); } }

tap is the read-only operator: it observes the stream without changing it. map would rewrite the value, which is how an envelope interceptor adds { data: ... } around every success in one place. The error callback fires for thrown exceptions, so failures are logged with their status.

Output of 111
LOG  [HTTP] GET /api/v1/books?limit=2&page=2 200 2.1ms id=197382b5
WARN [HTTP] GET /api/v1/books?limit=999&sort=pages 422 1.6ms id=95c522f6
LOG  [HTTP] POST /api/v1/books 201 0.6ms id=043ee7bd
WARN [HTTP] POST /api/v1/books/bk_3/reviews 409 1.0ms id=7581f7ad   [11 lines, 4 shown]

The id is set by middleware and read by the interceptor and the exception filter, which is why every log line and every error payload in this section carries the same eight characters. Choosing between the layers comes down to what each can see: middleware has req, res and next but not the handler; a guard has the handler and its metadata, and can only allow or refuse; an interceptor has the return stream too.