Bird
Raised Fist0
NestJSframework~15 mins

Interceptor interface in NestJS - Deep Dive

Choose your learning style10 modes available
Overview - Interceptor interface
What is it?
In NestJS, an Interceptor is a special class that can intercept and modify the flow of requests and responses in your application. The Interceptor interface defines how these classes should behave, allowing you to add extra logic before or after a method runs. This helps you handle tasks like logging, transforming data, or handling errors in a clean and reusable way.
Why it matters
Without interceptors, you would have to repeat common tasks like logging or error handling inside every controller or service method. This would make your code messy and hard to maintain. Interceptors let you write this logic once and apply it everywhere, making your app cleaner, easier to understand, and faster to update.
Where it fits
Before learning about interceptors, you should understand basic NestJS concepts like controllers, providers, and middleware. After mastering interceptors, you can explore advanced topics like custom decorators, guards, and exception filters to build robust applications.
Mental Model
Core Idea
An interceptor is like a checkpoint that can watch, change, or act on data before it reaches its destination or after it leaves.
Think of it like...
Imagine a security guard at a building entrance who checks and sometimes changes what people carry in or out. The guard can stop, inspect, or add something before letting them pass.
┌───────────────┐
│ Incoming Call │
└──────┬────────┘
       │
┌──────▼────────┐
│  Interceptor  │
│ (pre-process) │
└──────┬────────┘
       │
┌──────▼────────┐
│   Handler     │
│ (business)    │
└──────┬────────┘
       │
┌──────▼────────┐
│  Interceptor  │
│ (post-process)│
└──────┬────────┘
       │
┌──────▼────────┐
│  Response Out │
└───────────────┘
Build-Up - 7 Steps
1
FoundationWhat is an Interceptor in NestJS
🤔
Concept: Introduces the basic idea of interceptors as classes that can modify request and response flow.
In NestJS, an interceptor is a class that implements the NestInterceptor interface. It can run code before and after the execution of a route handler or method. This lets you add extra behavior like logging or transforming data without changing the handler itself.
Result
You understand that interceptors act as middleware but with more control over the method execution.
Understanding interceptors as a way to add reusable logic around method calls helps keep your code clean and modular.
2
FoundationInterceptor Interface Structure
🤔
Concept: Shows the shape and required method of the Interceptor interface.
The Interceptor interface requires implementing an intercept() method. This method receives two arguments: ExecutionContext (info about the current request) and CallHandler (which lets you control the method execution). The intercept() method returns an Observable that you can manipulate.
Result
You know the exact method signature and parameters needed to create an interceptor.
Knowing the intercept() method signature is key to writing any custom interceptor.
3
IntermediateUsing RxJS Observables in Interceptors
🤔Before reading on: do you think interceptors work synchronously or asynchronously? Commit to your answer.
Concept: Explains how interceptors use RxJS Observables to handle asynchronous operations and modify responses.
The intercept() method returns an Observable from CallHandler.handle(). You can use RxJS operators like map() to transform the response data or tap() to perform side effects like logging. This lets interceptors work smoothly with async code and streams.
Result
You can write interceptors that modify or log responses asynchronously.
Understanding RxJS in interceptors unlocks powerful ways to manipulate data streams and side effects.
4
IntermediateApplying Interceptors Globally and Locally
🤔Before reading on: do you think interceptors apply only to one route or can they affect the whole app? Commit to your answer.
Concept: Shows how to attach interceptors to specific routes, controllers, or globally for the entire app.
You can use the @UseInterceptors() decorator on methods or controllers to apply interceptors locally. To apply an interceptor globally, you register it in the main app module using app.useGlobalInterceptors(). This flexibility lets you control the scope of your logic.
Result
You can control where interceptors run, making your app more efficient and organized.
Knowing how to scope interceptors prevents unnecessary processing and keeps your app performant.
5
AdvancedChaining Multiple Interceptors
🤔Before reading on: if multiple interceptors are applied, do you think they run in parallel or in a specific order? Commit to your answer.
Concept: Explains how multiple interceptors run in a stacked order and how their execution flows.
When multiple interceptors are applied, they run in the order they are declared. Each interceptor can modify the data before passing it to the next. After the handler runs, the interceptors process the response in reverse order. This creates a layered effect similar to nested wrappers.
Result
You understand how to combine interceptors to build complex behaviors.
Knowing the execution order helps avoid bugs and design predictable interceptor chains.
6
AdvancedError Handling Inside Interceptors
🤔Before reading on: do you think interceptors can catch and handle errors from handlers? Commit to your answer.
Concept: Shows how interceptors can catch errors thrown by handlers and modify error responses.
Using RxJS catchError operator inside intercept() lets interceptors catch exceptions from the handler. You can then transform or log errors before sending a response. This centralizes error handling logic outside controllers.
Result
You can build interceptors that improve error reporting and recovery.
Understanding error handling in interceptors helps build more resilient applications.
7
ExpertPerformance Implications and Internal Flow
🤔Before reading on: do you think interceptors add significant overhead to requests? Commit to your answer.
Concept: Explores how interceptors affect request performance and the internal call flow in NestJS.
Interceptors add a small overhead because they wrap method calls and use Observables. Internally, NestJS calls interceptors in a chain, passing control and data through each. Efficient interceptor design avoids heavy computations or blocking calls to keep performance high.
Result
You can write interceptors that balance functionality and speed.
Knowing the internal flow and cost of interceptors helps optimize critical paths in production.
Under the Hood
NestJS uses a chain of interceptors that wrap around the route handler method. When a request comes in, NestJS creates an ExecutionContext object with request details and passes it to the first interceptor's intercept() method. Each interceptor calls CallHandler.handle() to invoke the next interceptor or the actual handler, returning an Observable. This Observable stream can be transformed or tapped by each interceptor before the final response is sent back.
Why designed this way?
Interceptors were designed to provide a flexible, reusable way to add cross-cutting concerns like logging, transformation, and error handling without cluttering business logic. Using Observables and a chain pattern allows asynchronous and synchronous code to work seamlessly. This design follows the decorator pattern, enabling stacking and composition of behaviors.
┌───────────────┐
│ Incoming Req  │
└──────┬────────┘
       │
┌──────▼────────┐
│ Interceptor 1 │
│ intercept()  │
└──────┬────────┘
       │ calls
┌──────▼────────┐
│ Interceptor 2 │
│ intercept()  │
└──────┬────────┘
       │ calls
┌──────▼────────┐
│  Handler      │
│ handle()     │
└──────┬────────┘
       │ returns Observable
┌──────▼────────┐
│ Interceptor 2 │
│ post-process │
└──────┬────────┘
       │
┌──────▼────────┐
│ Interceptor 1 │
│ post-process │
└──────┬────────┘
       │
┌──────▼────────┐
│ Response Sent │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do interceptors run before or after the route handler? Commit to your answer.
Common Belief:Interceptors only run before the route handler executes.
Tap to reveal reality
Reality:Interceptors run both before and after the route handler, allowing pre-processing and post-processing of data.
Why it matters:Thinking interceptors only run before causes missed opportunities to transform responses or handle errors after the handler.
Quick: Can interceptors modify the response data? Commit to yes or no.
Common Belief:Interceptors cannot change the response data; they only observe it.
Tap to reveal reality
Reality:Interceptors can modify or transform the response data by using RxJS operators on the Observable returned from the handler.
Why it matters:Believing interceptors can't modify data limits their use and leads to duplicated transformation logic in controllers.
Quick: Are interceptors and middleware the same in NestJS? Commit to yes or no.
Common Belief:Interceptors and middleware are the same and interchangeable.
Tap to reveal reality
Reality:Interceptors are different from middleware; interceptors work at the method execution level with access to response streams, while middleware runs before routing and cannot modify responses.
Why it matters:Confusing them leads to wrong usage and missed benefits of each, causing harder-to-maintain code.
Quick: Do interceptors always run in the order they are declared? Commit to yes or no.
Common Belief:Interceptors run in any random order regardless of declaration.
Tap to reveal reality
Reality:Interceptors run in the exact order they are declared, creating a predictable chain of execution.
Why it matters:Misunderstanding order causes bugs when interceptors depend on each other's output.
Expert Zone
1
Interceptors can be combined with exception filters to create powerful error handling pipelines that both catch and transform errors.
2
Using interceptors with async/await inside handlers requires careful handling of Observables to avoid unexpected behavior or memory leaks.
3
Global interceptors affect every request, so adding heavy logic there can degrade performance; selective application is often better.
When NOT to use
Avoid using interceptors for tasks better suited to middleware, such as modifying raw HTTP requests or responses before routing. For authentication and authorization, guards are more appropriate. For error handling, exception filters provide clearer semantics. Use interceptors mainly for cross-cutting concerns around method execution.
Production Patterns
In production, interceptors are commonly used for logging request and response data, transforming outgoing responses to a standard format, caching responses, and measuring performance metrics. They are also used to implement retry logic or timeout handling around service calls.
Connections
Middleware
Related but different layer in request processing
Understanding middleware helps clarify that interceptors operate deeper, around method execution, while middleware works earlier in the request lifecycle.
Decorator Pattern (Software Design)
Interceptors implement the decorator pattern
Knowing the decorator pattern explains how interceptors wrap and extend method behavior without changing original code.
Assembly Line (Manufacturing)
Interceptors act like quality control stations on an assembly line
Seeing interceptors as checkpoints in a production line helps understand their role in inspecting and modifying data step-by-step.
Common Pitfalls
#1Trying to modify response data without using RxJS operators.
Wrong approach:intercept(context, next) { const response = next.handle(); response.data = 'changed'; // wrong: response is Observable return response; }
Correct approach:intercept(context, next) { return next.handle().pipe( map(data => 'changed') ); }
Root cause:Misunderstanding that next.handle() returns an Observable, not the actual data.
#2Applying heavy synchronous logic inside interceptors causing slow responses.
Wrong approach:intercept(context, next) { for(let i=0; i<1e9; i++) {} // heavy loop return next.handle(); }
Correct approach:intercept(context, next) { return next.handle(); // keep interceptors lightweight }
Root cause:Not realizing interceptors run on every request and blocking code hurts performance.
#3Confusing interceptors with middleware and trying to modify raw HTTP request objects inside interceptors.
Wrong approach:intercept(context, next) { const req = context.switchToHttp().getRequest(); req.url = '/changed'; // ineffective here return next.handle(); }
Correct approach:Use middleware to modify raw HTTP requests before routing, not interceptors.
Root cause:Misunderstanding the lifecycle and responsibilities of middleware vs interceptors.
Key Takeaways
Interceptors in NestJS let you add reusable logic before and after method execution, keeping your code clean.
They use the intercept() method with Observables to handle asynchronous data streams and transformations.
Interceptors can be applied locally to routes or globally to the whole app, giving you flexible control.
Multiple interceptors run in a predictable order, wrapping around the handler like layers of an onion.
Understanding the difference between interceptors, middleware, guards, and filters is key to using them effectively.

Practice

(1/5)
1. What is the primary purpose of an interceptor in NestJS?
easy
A. To run code before and after a request is handled
B. To define database schemas
C. To handle HTTP routing
D. To manage user authentication

Solution

  1. Step 1: Understand the role of interceptors

    Interceptors in NestJS are designed to execute code before and after the main request handler runs.
  2. Step 2: Compare with other options

    Database schemas, routing, and authentication are handled by other parts of NestJS, not interceptors.
  3. Final Answer:

    To run code before and after a request is handled -> Option A
  4. Quick Check:

    Interceptor purpose = run code before/after request [OK]
Hint: Interceptors wrap request handling with extra code [OK]
Common Mistakes:
  • Confusing interceptors with middleware
  • Thinking interceptors handle routing
  • Assuming interceptors manage authentication
2. Which method must be implemented when creating a NestJS interceptor?
easy
A. handle()
B. intercept()
C. execute()
D. process()

Solution

  1. Step 1: Recall the interceptor interface

    The NestJS interceptor interface requires implementing the intercept() method.
  2. Step 2: Verify other options

    Methods like handle(), execute(), or process() are not part of the interceptor interface.
  3. Final Answer:

    intercept() -> Option B
  4. Quick Check:

    Interceptor method = intercept() [OK]
Hint: Remember: intercept() runs your interceptor logic [OK]
Common Mistakes:
  • Using handle() instead of intercept()
  • Confusing with middleware method names
  • Forgetting to implement intercept()
3. Given this interceptor code snippet, what will be logged?
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'))
    );
  }
}
medium
A. Only 'Before handler' is logged
B. Only 'After handler' is logged
C. 'Before handler' then 'After handler' are logged
D. No logs are produced

Solution

  1. Step 1: Analyze the intercept method

    The interceptor logs 'Before handler' immediately, then calls next.handle() which returns an Observable.
  2. Step 2: Understand the tap operator

    The tap operator logs 'After handler' when the Observable emits after the request handler finishes.
  3. Final Answer:

    'Before handler' then 'After handler' are logged -> Option C
  4. Quick Check:

    Logs before and after handler = 'Before handler' then 'After handler' are logged [OK]
Hint: tap() logs after the handler completes [OK]
Common Mistakes:
  • Thinking only one log appears
  • Ignoring the Observable pipe behavior
  • Assuming synchronous logging only
4. What is wrong with this interceptor implementation?
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class ErrorInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    try {
      return next.handle();
    } catch (error) {
      console.error('Error caught:', error);
      throw error;
    }
  }
}
medium
A. The interceptor should not throw errors
B. The intercept method must return void, not Observable
C. Missing call to next.handle() inside try block
D. The try-catch block won't catch async errors from next.handle()

Solution

  1. Step 1: Understand async error handling in interceptors

    next.handle() returns an Observable, so errors happen asynchronously and are not caught by try-catch.
  2. Step 2: Identify the problem with the try-catch

    The try-catch only catches synchronous errors, so async errors must be handled with RxJS operators like catchError.
  3. Final Answer:

    The try-catch block won't catch async errors from next.handle() -> Option D
  4. Quick Check:

    Async errors need RxJS catchError, not try-catch [OK]
Hint: Use RxJS catchError for async errors, not try-catch [OK]
Common Mistakes:
  • Using try-catch for async Observable errors
  • Assuming intercept returns void
  • Not handling errors inside pipe
5. You want to create an interceptor that measures how long a request takes and adds this duration to the response object as response.duration. Which approach correctly implements this?
hard
A. Record start time, call next.handle(), then use tap() to calculate duration and modify response before returning
B. Record start time, use try-catch around next.handle(), then set duration after catch
C. Modify response before calling next.handle(), then record duration after
D. Use a middleware instead of interceptor to measure time

Solution

  1. Step 1: Understand timing with interceptors

    Start time must be recorded before calling next.handle(), which returns an Observable.
  2. Step 2: Use tap() to run code after handler finishes

    tap() lets you calculate duration after the response is ready and modify it safely.
  3. Step 3: Evaluate options

    Record start time, call next.handle(), then use tap() to calculate duration and modify response before returning correctly uses tap() after next.handle() to set response.duration. Record start time, use try-catch around next.handle(), then set duration after catch uses try-catch incorrectly for async. Modify response before calling next.handle(), then record duration after modifies response too early. Use a middleware instead of interceptor to measure time suggests middleware, which is not the question focus.
  4. Final Answer:

    Record start time, call next.handle(), then use tap() to calculate duration and modify response before returning -> Option A
  5. Quick Check:

    Use tap() after next.handle() to measure time [OK]
Hint: Use tap() after next.handle() to add timing info [OK]
Common Mistakes:
  • Trying to catch async errors with try-catch
  • Modifying response before handler runs
  • Confusing middleware with interceptor roles
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard