Interceptors let you add extra steps that run before or after your main code. This helps you handle common tasks in one place, so you don't repeat code everywhere.
Why interceptors add cross-cutting logic in NestJS
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { console.log('Before handler'); const now = Date.now(); return next .handle() .pipe( tap(() => console.log(`After handler... ${Date.now() - now}ms`)), ); } }
The intercept method runs before and after the main handler.
You must return next.handle() to continue the request flow.
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Start');
return next.handle();
}intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
tap(() => console.log('After handler')),
);
}intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const start = Date.now();
return next.handle().pipe(
tap(() => console.log(`Took ${Date.now() - start}ms`)),
);
}This example shows a controller with a TimerInterceptor. The interceptor logs how long the request took. When you call the /hello route, it returns 'Hello, world!' and logs the time.
import { Controller, Get, UseInterceptors, Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() class TimerInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const start = Date.now(); return next.handle().pipe( tap(() => console.log(`Request took ${Date.now() - start}ms`)), ); } } @Controller('hello') @UseInterceptors(TimerInterceptor) export class HelloController { @Get() sayHello(): string { return 'Hello, world!'; } }
Interceptors work like a sandwich: code before and after your main logic.
They help keep your code clean by handling common tasks in one place.
Remember to always call next.handle() to continue the flow.
Interceptors add shared logic before and after your main code.
They are great for logging, timing, error handling, and more.
Using interceptors keeps your app organized and easier to maintain.
Practice
Interceptors help to add logic that applies to many parts of the app without repeating code. Why is this useful?Solution
Step 1: Understand the role of interceptors
Interceptors run code before and after the main handler, allowing shared tasks like logging or error handling.Step 2: Compare with other options
Controllers handle requests, databases store data, and UI components are unrelated to interceptors.Final Answer:
They add shared logic before and after main code, like logging or timing. -> Option AQuick Check:
Interceptors add shared logic = A [OK]
- Thinking interceptors handle HTTP requests directly
- Confusing interceptors with database or UI roles
- Assuming interceptors replace controllers
Solution
Step 1: Recall NestJS interceptor pattern
Interceptors must implement theNestInterceptorinterface and define theintercept()method.Step 2: Check other options
Controllers use@Controller()andhandle()is not an interceptor method; services need methods; plain functions lack interface.Final Answer:
Implement the NestInterceptor interface and define the intercept() method. -> Option AQuick Check:
Interceptor interface + intercept() = B [OK]
- Confusing interceptors with controllers or services
- Missing the intercept() method
- Not using the NestInterceptor interface
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before handler');
return next.handle().pipe(
tap(() => console.log('After handler'))
);
}
}What will be printed to the console when a request is handled?
Solution
Step 1: Analyze the intercept method flow
The interceptor logs 'Before handler' immediately, then callsnext.handle()which runs the main handler.Step 2: Understand the tap operator
Thetapoperator runs after the main handler completes, logging 'After handler'.Final Answer:
Before handler, then After handler -> Option BQuick Check:
Logs before and after handler = C [OK]
- Thinking only one log prints
- Confusing order of logs
- Ignoring the tap() operator effect
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class SilentInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Intercepted');
next.handle();
}
}What is the problem?
Solution
Step 1: Check the intercept method return
The intercept method must return the observable fromnext.handle()to continue the request flow.Step 2: Identify missing return
Here,next.handle()is called but its result is not returned, so the request does not proceed and logs may not appear.Final Answer:
The intercept method does not return the observable from next.handle(). -> Option DQuick Check:
Missing return next.handle() = D [OK]
- Forgetting to return next.handle()
- Misplacing console.log after next.handle()
- Confusing decorators for interceptors
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class TimingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const start = Date.now();
return next.handle().pipe(
tap(() => {
const elapsed = Date.now() - start;
console.log(`Request took ${elapsed}ms`);
})
);
}
}Solution
Step 1: Understand timing measurement
The interceptor records start time before callingnext.handle()and usestap()to run code after the handler completes.Step 2: Confirm correct usage of RxJS tap()
Thetap()operator runs side effects after the observable emits, so it correctly logs elapsed time after the request finishes.Final Answer:
This interceptor correctly measures time before and after the handler using tap(). -> Option CQuick Check:
Timing with tap() after handler = A [OK]
- Measuring time only before handler
- Not returning next.handle() observable
- Using setTimeout instead of RxJS operators
Ready to prove it?
Pass the test and this pattern moves unknown
