Using Interceptors and Exception Filters in NestJS Without Confusion

By this point in the series, your NestJS app can already do a lot:

  • it has structure
  • it understands modules, controllers, and providers
  • it uses dependency injection
  • it validates input with pipes and DTOs
  • it protects routes with guards

Nice.

But now we reach a part where a lot of beginners start mixing concepts together:

interceptors and exception filters

This happens because both of them feel like they are “doing something around the request.”

Which is true.

But they are not the same thing.

Not even close, really.

If you use them well:

  • interceptors help shape success
  • exception filters help shape failure

That is the simple version.

In this article, we will make the difference crystal clear.

By the end, you will understand:

  • what an interceptor does
  • what an exception filter does
  • when to use one and not the other
  • why interceptors are great for response wrapping
  • why exception filters are great for error handling
  • what beginners often get wrong

Let’s untangle the two tools that everyone eventually confuses at least once.


Why These Two Get Confused So Often

From a beginner’s perspective, both interceptors and exception filters can feel like “special framework layers that do something before or after the controller.”

That is exactly why people mix them up.

But the clean distinction is this:

  • Interceptors wrap execution and usually help with cross-cutting behavior, especially around successful request handling and response shaping
  • Exception filters step in when something throws and help shape the error response

Nest’s interceptor docs say interceptors can:

  • bind extra logic before or after method execution
  • transform the result returned from a function
  • transform the exception thrown from a function
  • extend basic function behavior
  • even completely override a function in some cases, such as caching

Nest’s exception filter docs say Nest has a built-in exceptions layer that processes unhandled exceptions across the application and sends appropriate user-friendly responses.

So yes, both sit around request handling.

But they serve very different jobs.


What Is an Interceptor in NestJS?

Nest’s docs define an interceptor as a class decorated with @Injectable() that implements the NestInterceptor interface. They are inspired by aspect-oriented programming and can run logic before and after a handler executes.

That means an interceptor is basically a smart wrapper around route execution.

You can use it to:

  • log request timing
  • wrap response data
  • transform returned values
  • apply cross-cutting behavior
  • cache results
  • add consistency across routes

A simple mental model:

An interceptor sits around the route handler and can affect what happens before and after it runs.

That is what makes interceptors special.

They are not just “before” tools.
They are not just “after” tools.

They are around tools.


What Is an Exception Filter in NestJS?

Nest’s docs say Nest includes a built-in exceptions layer that catches unhandled exceptions across an application and automatically sends a response. Out of the box, a built-in global exception filter handles HttpException and its subclasses, and unknown errors default to a JSON response with 500 and "Internal server error".

So an exception filter is for one main purpose:

handling errors in a clean and controlled way

That means:

  • catching thrown exceptions
  • customizing error responses
  • standardizing error format
  • avoiding messy or inconsistent failure output

A simple mental model:

An exception filter shapes what the client sees when something goes wrong.

That is its lane.

Not success responses.
Not logging every request.
Not response timing.

Errors.


Where Interceptors and Exception Filters Fit in the Request Lifecycle

Nest’s request lifecycle FAQ says a request generally moves through:

middleware → guards → interceptors → pipes → controller/handler → back through interceptors on the response path.

That means interceptors are part of the normal forward-and-return flow.

They actively wrap the request/response cycle.

Exception filters are different.

They are not just “the next normal step.”

They are part of the exception handling layer that catches thrown errors when execution fails. Nest’s exception filter docs describe this built-in exceptions layer as handling unhandled exceptions globally.

So the simplest way to remember the lifecycle is:

  • interceptors = part of the normal route execution flow
  • exception filters = part of the error handling flow

That distinction matters a lot.


What Interceptors Are Great For

Nest’s interceptor docs explicitly list common capabilities such as result transformation, extending behavior, and adding logic before and after execution.

Here are the best beginner use cases.

1. Response Wrapping

This is probably the most common example.

Suppose your controller returns:

{ "id": 1, "name": "Alice" }

An interceptor can wrap it into:

{
  "success": true,
  "data": { "id": 1, "name": "Alice" }
}

Now your API responses have a consistent shape.

That is a classic interceptor job.


2. Logging Execution Time

You can use an interceptor to:

  • start timing before the handler runs
  • let the handler execute
  • measure how long it took
  • log the duration

That is much cleaner than putting timing logic in every controller.


3. Transforming Returned Data

Interceptors can map the returned value into a different format.

For example:

  • hiding internal fields
  • renaming properties
  • formatting a response envelope
  • trimming down output

Very useful for API consistency.


4. Caching Behavior

Nest’s docs mention that interceptors can completely override execution in some cases, such as caching.

This is more advanced, but it shows how powerful interceptors can become.


A Simple Response Interceptor Example

Here is a beginner-friendly response wrapper:

import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from '@nestjs/common';
import { map, Observable } from 'rxjs';

@Injectable()
export class ResponseInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data) => ({
        success: true,
        data,
      })),
    );
  }
}

This matches Nest’s interceptor model: intercept() receives an ExecutionContext and a CallHandler, and next.handle() invokes the route handler so the interceptor can work with the result stream.

Then apply it like this:

import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { ResponseInterceptor } from './response.interceptor';

@Controller('users')
@UseInterceptors(ResponseInterceptor)
export class UsersController {
  @Get()
  findAll() {
    return [{ id: 1, name: 'Alice' }];
  }
}

Now the controller stays simple, and the response formatting is centralized.

That is exactly the kind of separation you want.


What Exception Filters Are Great For

Exception filters are for one main thing:

turning thrown errors into cleaner, more controlled responses

Nest’s exception filter docs say the built-in global exception filter already handles HttpException and its subclasses, and unknown exceptions become a 500 Internal server error response by default.

That means exception filters are especially useful when you want to:

1. Standardize Error Responses

Instead of getting random-shaped errors from different parts of the app, you can make them consistent.

2. Handle Custom Exceptions Nicely

If you throw a custom application exception, a filter can decide exactly how to present it to the client.

3. Improve Error Readability

A cleaner error response helps frontend developers, API consumers, and honestly your future self.

4. Keep Controllers and Services Cleaner

Instead of formatting every error manually in many places, you centralize the error response behavior.

Very nice.


A Simple Exception Filter Example

A basic HTTP exception filter often uses @Catch() and ArgumentsHost, which Nest’s docs describe as the object that lets you access the current arguments across application contexts, including HTTP.

Example:

import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
} from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status = exception.getStatus();

    response.status(status).json({
      success: false,
      statusCode: status,
      path: request.url,
      message: exception.message,
    });
  }
}

Then apply it like this:

import { Controller, Get, UseFilters, BadRequestException } from '@nestjs/common';
import { HttpExceptionFilter } from './http-exception.filter';

@Controller('users')
@UseFilters(HttpExceptionFilter)
export class UsersController {
  @Get()
  findAll() {
    throw new BadRequestException('Something went wrong');
  }
}

Now your app can return more structured error responses when HTTP exceptions are thrown.

That is the job.


The Core Difference in One Sentence

If you remember only one thing from this whole chapter, remember this:

Interceptors shape execution and responses. Exception filters shape thrown errors.

That is the cleanest possible summary.


Can Interceptors Handle Errors Too?

This is where things get a little subtle.

Nest’s interceptor docs say interceptors can also transform exceptions thrown from a function.

So yes, interceptors can interact with errors.

But that does not mean they replace exception filters.

The practical difference is:

  • interceptors are best when you want to wrap the execution flow, transform results, or apply cross-cutting behavior
  • exception filters are best when your main goal is error handling and error response formatting

That is the safer beginner rule.

Could you do some error-related logic inside an interceptor?

Yes.

Should you treat interceptors as your main dedicated error response layer?

Usually no.

Exception filters are the more natural tool for that.


Why Response Formatting Belongs in Interceptors, Not Controllers

A very common beginner pattern looks like this:

@Get()
findAll() {
  const users = this.usersService.findAll();

  return {
    success: true,
    data: users,
  };
}

This works.

But if you do it in every controller method, you are repeating yourself a lot.

Interceptors solve that nicely by wrapping returned data in one place.

Nest’s docs specifically call out “transform the result returned from a function” as one of the main interceptor capabilities.

So if the concern is:

  • response envelopes
  • output mapping
  • consistent structure
  • timing logs

that usually belongs in an interceptor, not the controller.

Controllers should stay focused on the route logic itself.


Why Error Formatting Belongs in Exception Filters, Not Controllers

Now imagine this controller style:

@Get(':id')
findOne() {
  try {
    return this.usersService.findOne();
  } catch (error) {
    return {
      success: false,
      message: 'Something failed',
    };
  }
}

You can do that.

But if every controller starts manually formatting errors, your app gets inconsistent very fast.

Nest already has an exceptions layer, and exception filters are the official way to customize how thrown exceptions are turned into responses.

That is a much cleaner architecture.


Applying Interceptors and Filters

Just like guards, both interceptors and filters can be bound at different levels in Nest using decorators and global binding. Nest’s docs show filters and interceptors being bound method-scoped, controller-scoped, or globally.

That means you can apply them to:

  • one route
  • one controller
  • the whole app

Method-level

Great when you want behavior for a single route.

Controller-level

Great when the whole feature area should behave the same way.

Global

Great for app-wide response formatting or app-wide exception handling.

This flexibility is part of why Nest feels so scalable.


Common Beginner Mistakes

Let’s save you some confusion.

1. Using Exception Filters for Normal Successful Responses

That is not their job.

If you want to shape normal responses, use interceptors.

2. Using Interceptors as the Main Error Response Layer

Interceptors can interact with thrown exceptions, but dedicated error shaping belongs more naturally in exception filters. Nest’s exceptions layer is built specifically for that.

3. Stuffing Too Much into Controllers

If your controllers are formatting every response and every error manually, they are doing too much.

4. Forgetting That Interceptors Wrap Both Sides

Interceptors are powerful because they can run before and after handler execution. That is why they are so useful for transformation and timing.

5. Expecting Filters to Run Like Normal Middleware

They do not sit in the same normal forward request path. They are part of the exception handling layer when something throws.


A Good Mental Model to Remember

Here is the easiest mental shortcut:

  • Interceptor = wraps the route execution
  • Exception filter = catches and formats thrown errors

Or even shorter:

  • Interceptors shape success
  • Exception filters shape failure

That sentence will save you a lot of confusion.


A Real-World Example Flow

Let’s say a request hits:

GET /users/42

Case 1: Success

  • request reaches the controller
  • the service returns user data
  • a response interceptor wraps it into a standard API format
  • the client receives a clean success response

Case 2: Failure

  • request reaches the controller
  • the service throws NotFoundException
  • Nest’s exceptions layer catches it
  • your exception filter formats it into a cleaner error response
  • the client receives a structured failure response

That is the split in action.

Very different responsibilities.
Very different tools.


Why This Matters for Real Projects

Once your app grows, consistency starts to matter a lot.

You want:

  • success responses to look predictable
  • error responses to look predictable
  • controllers to stay small
  • cross-cutting concerns to live in reusable layers

That is exactly why interceptors and exception filters matter.

They help you move those concerns out of route handlers and into the right framework tools.

Which is very NestJS.

And very worth learning early.


Final Thoughts

Interceptors and exception filters both sit around the request/response story, but they are not doing the same job.

Interceptors are for wrapping execution and shaping responses. Nest’s docs explicitly say they can run logic before and after method execution and transform returned values.

Exception filters are for handling thrown errors and shaping failure responses. Nest’s docs describe them as part of the built-in exceptions layer that processes unhandled exceptions across the app.

So the easy rule is:

  • use interceptors for success-side cross-cutting behavior
  • use exception filters for error-side control

That is the clean mental split.

And now that you understand the core request lifecycle tools — pipes, guards, interceptors, and exception filters — the next step is building something more practical with them:

your first real REST API in NestJS

Because that is where all these pieces stop feeling theoretical and start feeling useful.


Real Interview Questions

What is an interceptor in NestJS?

An interceptor is a class that implements NestInterceptor and can run logic before and after method execution, transform returned data, transform exceptions, extend behavior, or even override execution in some cases.

What is an exception filter in NestJS?

An exception filter is a class used to catch and handle thrown exceptions in a structured way. Nest also provides a built-in global exceptions layer for unhandled exceptions.

When should I use an interceptor in NestJS?

Use an interceptor when you want to wrap route execution, transform responses, log timings, or apply reusable cross-cutting behavior.

When should I use an exception filter in NestJS?

Use an exception filter when you want to customize how errors are turned into responses and keep failure output consistent.

Can an interceptor handle errors too?

It can interact with thrown exceptions, but exception filters are the more natural and dedicated tool for structured error response handling. Nest’s interceptor docs mention exception transformation, while the exception filter docs describe the dedicated exceptions layer.

Subscribe for new post. No spam, just tech.