NestJS Exception Handling Tutorial – Filters and Custom Errors
No matter how carefully you write validation rules and business logic, real applications will always encounter errors: a resource that doesn't exist, a database connection that fails, or a business rule that gets violated. How your API responds to these situations, consistently and informatively, or chaotically and confusingly, has a huge impact on how usable and professional it feels to the developers consuming it.
This lesson covers NestJS's built-in HTTP exception classes, how the framework's default exception handling works out of the box, and how to build custom exception filters when you need more control over exactly how errors are formatted and logged.
NestJS Exception Handling: Learning Objectives
- Use NestJS's built-in HTTP exception classes to return meaningful error responses.
- Understand how NestJS's default exception handling works without any custom code.
- Build a custom exception filter using the @Catch() decorator.
- Apply exception filters at the method, controller, or global level.
- Design consistent, structured error response formats across an entire API.
NestJS Exception Handling: Key Terms and Definitions
- Exception filter: A class responsible for catching exceptions thrown during request processing and formatting the resulting HTTP response.
- HttpException: The base class NestJS provides for representing errors with an associated HTTP status code.
- Built-in HTTP exceptions: Convenience classes like NotFoundException, BadRequestException, and UnauthorizedException that extend HttpException with pre-set status codes.
- @Catch() decorator: A decorator used on a custom exception filter class to specify which type of exception it should handle.
- ExceptionsHandler: NestJS's internal, built-in mechanism that automatically catches any unhandled exception and formats a default JSON error response.
How NestJS Exception Handling Works: Detailed Explanation
NestJS actually has a fairly sophisticated exception handling system built in by default, meaning even without writing any custom error-handling code, your application already behaves reasonably well when something goes wrong. If a route handler throws any exception, including one you throw deliberately, NestJS's built-in ExceptionsHandler catches it and formats a JSON response containing a statusCode, a message, and typically the error name, defaulting to a 500 Internal Server Error status if the exception type isn't otherwise recognized.
To return more specific, meaningful error responses, NestJS provides a set of built-in exception classes that all extend a base HttpException class, each pre-configured with an appropriate status code. NotFoundException corresponds to a 404 status, BadRequestException to 400, UnauthorizedException to 401, ForbiddenException to 403, and ConflictException to 409, among others. Throwing throw new NotFoundException('User not found') anywhere in your application, whether in a controller or a service, immediately produces a well-formatted 404 response with that exact message, without any additional configuration.
While these built-in exceptions cover the vast majority of everyday cases, sometimes you need more control over how errors are formatted, perhaps to match a specific API response contract required by frontend teams, or to add centralized logging whenever certain errors occur. This is exactly what custom exception filters are for. A custom exception filter is a class decorated with @Catch(), optionally specifying which exception type it should handle (or catching all exceptions if left empty), and implementing the ExceptionFilter interface's catch() method, which receives the thrown exception and the current execution context, giving you full control over the response's shape, status code, and any side effects like logging.
Once built, a custom exception filter can be applied at different scopes, similar to pipes: at a single route handler using @UseFilters(), at an entire controller, or globally across the whole application using app.useGlobalFilters() in main.ts. A global exception filter is extremely common in real production applications, ensuring that literally every error, whether a deliberate HttpException or an unexpected bug, gets formatted into the exact same consistent JSON shape before reaching the client, which makes life significantly easier for frontend developers and API consumers who can rely on one predictable error format everywhere.
Interview-Friendly Explanation
A strong interview or viva answer for nestjs exception handling must go beyond a one-line definition. Interviewers evaluating BCA, MCA, B.Tech, and self-taught backend developers reward answers that combine a precise definition, a clear working explanation, a real-world example, and a conclusion that highlights why it matters in production Node.js applications. Use the four-point framework below when answering under time pressure.
- Definition point: Exception filter: A class responsible for catching exceptions thrown during request processing and formatting the resulting HTTP response.
- Working point: Understand how NestJS's default exception handling works without any custom code.
- Example point: Public APIs consumed by external developers almost universally implement a global exception filter to guarantee one predictable error response shape, regardless of which internal error actually occurred.
- Conclusion point: NestJS's built-in exception handling already covers most everyday needs without any extra code, simply by throwing the right built-in exception class.
How to Answer This in a Technical Interview
- Give a two-to-three sentence definition using correct NestJS and Node.js terminology.
- Add one specific example drawn from a real backend scenario such as an e-commerce, fintech, or SaaS API.
- Mention the request lifecycle stage this concept operates at (pipe, guard, middleware, filter, interceptor) since interviewers often test whether you know the execution order.
- Close with one benefit, trade-off, or production use case.
- Avoid vague answers like "it just validates stuff" — interviewers filter these out immediately.
Practical Scenario
Imagine you are hardening a production API for a SaaS product, similar to systems used at companies like Razorpay, Freshworks, or Postman. Every lesson in this module maps directly to a decision you will make while making that backend secure, predictable, and resilient to bad input: validating what comes in, transforming it safely, catching failures gracefully, and protecting routes from unauthorized access.
NestJS Exception Handling: Architecture and Flow Diagram
Visualize an error handling flow:
[Route Handler or Service throws an exception] --> [NestJS's Exception Layer intercepts it] --> [Custom Exception Filter (if registered) formats the response] --> [Consistent JSON error response sent to client]
Example response shape: { "statusCode": 404, "message": "User not found", "timestamp": "...", "path": "/users/99" }
NestJS Exception Handling: Built-in Exception Reference Table
| Built-in Exception | HTTP Status | Typical Use Case |
|---|---|---|
| BadRequestException | 400 | Invalid input data or failed validation |
| UnauthorizedException | 401 | Missing or invalid authentication credentials |
| ForbiddenException | 403 | Authenticated but not permitted to perform the action |
| NotFoundException | 404 | Requested resource does not exist |
| ConflictException | 409 | Request conflicts with the current state, e.g. duplicate entry |
| InternalServerErrorException | 500 | Unexpected server-side failure |
NestJS Exception Handling: NestJS Code Example
// Using built-in exceptions directly in a service
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
@Injectable()
export class UsersService {
private users = [{ id: 1, email: 'asha@example.com' }];
findOne(id: number) {
const user = this.users.find((u) => u.id === id);
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
create(email: string) {
const exists = this.users.some((u) => u.email === email);
if (exists) {
throw new ConflictException('A user with this email already exists');
}
const newUser = { id: this.users.length + 1, email };
this.users.push(newUser);
return newUser;
}
}
// A custom global exception filter
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message =
exception instanceof HttpException
? exception.getResponse()
: 'Internal server error';
response.status(status).json({
success: false,
statusCode: status,
path: request.url,
timestamp: new Date().toISOString(),
message,
});
}
}
// main.ts — registering the filter globally
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new AllExceptionsFilter());
await app.listen(3000);
}
bootstrap();
UsersService throws NotFoundException and ConflictException directly from business logic whenever a specific condition is met, immediately producing correctly-coded HTTP responses without any manual status-code handling in the controller. AllExceptionsFilter then demonstrates a custom global exception filter using @Catch() with no arguments, meaning it catches every exception thrown anywhere in the application, whether a deliberate HttpException or a completely unexpected error. It inspects whether the exception is a recognized HttpException to extract the correct status and message, falling back to a generic 500 error otherwise, and formats every single error response into the exact same consistent JSON shape, including a timestamp and the request path, which is registered globally in main.ts so it applies application-wide.
Real-World NestJS Exception Handling Industry Examples
- Public APIs consumed by external developers almost universally implement a global exception filter to guarantee one predictable error response shape, regardless of which internal error actually occurred.
- E-commerce checkout flows throw ConflictException when a customer tries to purchase an out-of-stock item that was available moments earlier, giving the frontend a clear, specific status to handle.
- Banking and fintech APIs pair custom exception filters with centralized logging, ensuring every error, especially unexpected 500-level failures, is automatically logged for monitoring and alerting systems.
- Multi-tenant SaaS platforms often extend custom exception filters to strip sensitive internal error details (like stack traces or database error messages) before sending a response to the client, while still logging full details internally.
NestJS Exception Handling Interview Questions and Answers
Q1. What happens by default in NestJS when a route handler throws an exception without any custom exception filter?
Short answer: NestJS's built-in exception handling layer automatically catches the exception and formats a JSON error response containing a statusCode and message, defaulting to a 500 Internal Server Error for unrecognized exception types, or using the specific status code if a built-in HttpException subclass like NotFoundException was thrown.
Detailed explanation: NestJS actually has a fairly sophisticated exception handling system built in by default, meaning even without writing any custom error-handling code, your application already behaves reasonably well when something goes wrong. If a route handler throws any exception, including one you throw deliberately, NestJS's built-in ExceptionsHandler catches it and formats a JSON response containing a statusCode, a message, and typically the error name, defaulting to a 500 Internal Server Error status if the exception type isn't otherwise recognized. To return more specific, meaningful error responses, NestJS provides a set of built-in exception classes that all extend a base HttpException class, each pre-configured with an appropriate status code. NotFoundException corresponds to a 404 status, BadRequestException to 400, UnauthorizedException to 401, ForbiddenException to 403, and ConflictException to 409, among others. Throwing throw new NotFoundException('User not found') anywhere in your application, whether in a controller or a service, immediately produces a well-formatted 404 response with that exact message, without any additional configuration. While these built-in exceptions cover the vast majority of everyday cases, sometimes you need more control over how errors are formatted, perhaps to match a specific API response contract required by frontend teams, or to add centralized logging whenever certain errors occur. This is exactly what custom exception filters are for. A custom exception filter is a class decorated with @Catch(), optionally specifying which exception type it should handle (or catching all exceptions if left empty), and implementing the ExceptionFilter interface's catch() method, which receives the thrown exception and the current execution context, giving you full control over the response's shape, status code, and any side effects like logging. Once built, a custom exception filter can be applied at different scopes, similar to pipes: at a single route handler using @UseFilters(), at an entire controller, or globally across the whole application using app.useGlobalFilters() in main.ts. A global exception filter is extremely common in real production applications, ensuring that literally every error, whether a deliberate HttpException or an unexpected bug, gets formatted into the exact same consistent JSON shape before reaching the client, which makes life significantly easier for frontend developers and API consumers who can rely on one predictable error format everywhere.
Practical example: Public APIs consumed by external developers almost universally implement a global exception filter to guarantee one predictable error response shape, regardless of which internal error actually occurred.
Interview tip: NestJS automatically handles thrown exceptions even without custom filters, defaulting to 500 for unrecognized errors.
Revision hook: NestJS's built-in exception handling already covers most everyday needs without any extra code, simply by throwing the right built-in exception class.
Q2. What is the difference between throwing a plain Error and throwing a NotFoundException in NestJS?
Short answer: Throwing a plain JavaScript Error results in NestJS treating it as an unrecognized exception, typically resulting in a generic 500 Internal Server Error response. Throwing a NotFoundException, which extends NestJS's HttpException base class, automatically produces a properly formatted 404 response with your custom message, since NestJS recognizes and specifically handles HttpException subclasses.
Detailed explanation: UsersService throws NotFoundException and ConflictException directly from business logic whenever a specific condition is met, immediately producing correctly-coded HTTP responses without any manual status-code handling in the controller. AllExceptionsFilter then demonstrates a custom global exception filter using @Catch() with no arguments, meaning it catches every exception thrown anywhere in the application, whether a deliberate HttpException or a completely unexpected error. It inspects whether the exception is a recognized HttpException to extract the correct status and message, falling back to a generic 500 error otherwise, and formats every single error response into the exact same consistent JSON shape, including a timestamp and the request path, which is registered globally in main.ts so it applies application-wide.
Practical example: E-commerce checkout flows throw ConflictException when a customer tries to purchase an out-of-stock item that was available moments earlier, giving the frontend a clear, specific status to handle.
Interview tip: Built-in exceptions to remember: BadRequestException (400), UnauthorizedException (401), ForbiddenException (403), NotFoundException (404), ConflictException (409).
Revision hook: Custom exception filters exist for centralizing consistent formatting, logging, and control over exactly what error details reach the client.
Q3. How would you build a custom exception filter that catches every type of exception in a NestJS application?
Short answer: You create a class decorated with @Catch() (with no arguments, meaning it catches all exception types), implement the ExceptionFilter interface's catch(exception, host) method to extract the response object and format your desired error shape, and register it globally in main.ts using app.useGlobalFilters().
Detailed explanation: NestJS provides solid exception handling out of the box, automatically catching thrown exceptions and formatting a JSON response, defaulting to a 500 status for unrecognized errors. Built-in exception classes like NotFoundException, BadRequestException, and ConflictException, all extending the base HttpException class, let you throw meaningful, correctly-coded errors directly from controllers or services with a single line of code. For applications needing more consistency or centralized control, custom exception filters, built using the @Catch() decorator and the ExceptionFilter interface, allow you to format every error response in a single, predictable shape, optionally adding logging or stripping sensitive details, and can be applied at the method, controller, or, most commonly, global level across the entire application.
Practical example: Banking and fintech APIs pair custom exception filters with centralized logging, ensuring every error, especially unexpected 500-level failures, is automatically logged for monitoring and alerting systems.
Interview tip: @Catch() decorator plus the ExceptionFilter interface's catch() method define a custom exception filter.
Revision hook: Throwing meaningful, specific exceptions from your services (not just controllers) keeps error handling close to where problems actually occur.
Q4. Why would a company want a global exception filter instead of relying only on NestJS's default exception handling?
Short answer: A global exception filter guarantees a single, completely consistent error response format across every possible error in the application, including logging, custom fields like a timestamp or request path, and control over exactly what internal details, if any, are exposed to the client, none of which the default handling customizes.
Detailed explanation: NestJS actually has a fairly sophisticated exception handling system built in by default, meaning even without writing any custom error-handling code, your application already behaves reasonably well when something goes wrong. If a route handler throws any exception, including one you throw deliberately, NestJS's built-in ExceptionsHandler catches it and formats a JSON response containing a statusCode, a message, and typically the error name, defaulting to a 500 Internal Server Error status if the exception type isn't otherwise recognized. To return more specific, meaningful error responses, NestJS provides a set of built-in exception classes that all extend a base HttpException class, each pre-configured with an appropriate status code. NotFoundException corresponds to a 404 status, BadRequestException to 400, UnauthorizedException to 401, ForbiddenException to 403, and ConflictException to 409, among others. Throwing throw new NotFoundException('User not found') anywhere in your application, whether in a controller or a service, immediately produces a well-formatted 404 response with that exact message, without any additional configuration. While these built-in exceptions cover the vast majority of everyday cases, sometimes you need more control over how errors are formatted, perhaps to match a specific API response contract required by frontend teams, or to add centralized logging whenever certain errors occur. This is exactly what custom exception filters are for. A custom exception filter is a class decorated with @Catch(), optionally specifying which exception type it should handle (or catching all exceptions if left empty), and implementing the ExceptionFilter interface's catch() method, which receives the thrown exception and the current execution context, giving you full control over the response's shape, status code, and any side effects like logging. Once built, a custom exception filter can be applied at different scopes, similar to pipes: at a single route handler using @UseFilters(), at an entire controller, or globally across the whole application using app.useGlobalFilters() in main.ts. A global exception filter is extremely common in real production applications, ensuring that literally every error, whether a deliberate HttpException or an unexpected bug, gets formatted into the exact same consistent JSON shape before reaching the client, which makes life significantly easier for frontend developers and API consumers who can rely on one predictable error format everywhere.
Practical example: Multi-tenant SaaS platforms often extend custom exception filters to strip sensitive internal error details (like stack traces or database error messages) before sending a response to the client, while still logging full details internally.
Interview tip: app.useGlobalFilters() in main.ts applies an exception filter across the entire application.
Revision hook: A single global exception filter is a common, valuable pattern in nearly every serious production NestJS application.
NestJS Exception Handling MCQs and Practice Questions
1. Which built-in NestJS exception corresponds to an HTTP 404 status code?
- BadRequestException
- ForbiddenException
- NotFoundException
- ConflictException
Answer: C. NotFoundException
Explanation: NotFoundException is specifically pre-configured to return an HTTP 404 status code, conventionally used when a requested resource does not exist.
Concept link: NestJS automatically handles thrown exceptions even without custom filters, defaulting to 500 for unrecognized errors.
Why this matters: NestJS's built-in exception handling already covers most everyday needs without any extra code, simply by throwing the right built-in exception class.
2. What decorator is used to define which exceptions a custom exception filter should handle?
- @Filter()
- @Catch()
- @Exception()
- @HandleError()
Answer: B. @Catch()
Explanation: @Catch() is the decorator applied to a custom exception filter class, optionally specifying one or more exception types it should handle, or catching everything if left empty.
Concept link: Built-in exceptions to remember: BadRequestException (400), UnauthorizedException (401), ForbiddenException (403), NotFoundException (404), ConflictException (409).
Why this matters: Custom exception filters exist for centralizing consistent formatting, logging, and control over exactly what error details reach the client.
3. How do you register an exception filter globally across an entire NestJS application?
- @UseFilters() on every controller
- app.useGlobalFilters() in main.ts
- Adding it to the providers array
- It is global by default automatically
Answer: B. app.useGlobalFilters() in main.ts
Explanation: Calling app.useGlobalFilters() during application bootstrap in main.ts registers an exception filter so that it applies to every route across the entire application, not just a specific controller or method.
Concept link: @Catch() decorator plus the ExceptionFilter interface's catch() method define a custom exception filter.
Why this matters: Throwing meaningful, specific exceptions from your services (not just controllers) keeps error handling close to where problems actually occur.
4. What status code does NestJS return by default for an unrecognized, non-HttpException error?
- 400
- 404
- 422
- 500
Answer: D. 500
Explanation: When an exception that is not a recognized HttpException subclass is thrown, NestJS's default exception handling falls back to returning a 500 Internal Server Error status code.
Concept link: app.useGlobalFilters() in main.ts applies an exception filter across the entire application.
Why this matters: A single global exception filter is a common, valuable pattern in nearly every serious production NestJS application.
Common NestJS Exception Handling Mistakes to Avoid
- Throwing plain JavaScript Error objects instead of NestJS's built-in HttpException subclasses, losing the automatic correct status code mapping.
- Building a custom exception filter but forgetting to register it, either globally or on the relevant controller, meaning it never actually runs.
- Exposing raw internal error details, like database error messages or stack traces, directly to API clients through an exception filter, creating a potential security risk.
- Writing duplicate try-catch error formatting logic inside every controller method instead of centralizing this behavior in a single exception filter.
NestJS Exception Handling: Interview Notes and Exam Tips
- NestJS automatically handles thrown exceptions even without custom filters, defaulting to 500 for unrecognized errors.
- Built-in exceptions to remember: BadRequestException (400), UnauthorizedException (401), ForbiddenException (403), NotFoundException (404), ConflictException (409).
- @Catch() decorator plus the ExceptionFilter interface's catch() method define a custom exception filter.
- app.useGlobalFilters() in main.ts applies an exception filter across the entire application.
Key NestJS Exception Handling Takeaways
- NestJS's built-in exception handling already covers most everyday needs without any extra code, simply by throwing the right built-in exception class.
- Custom exception filters exist for centralizing consistent formatting, logging, and control over exactly what error details reach the client.
- Throwing meaningful, specific exceptions from your services (not just controllers) keeps error handling close to where problems actually occur.
- A single global exception filter is a common, valuable pattern in nearly every serious production NestJS application.
NestJS Exception Handling: Summary
NestJS provides solid exception handling out of the box, automatically catching thrown exceptions and formatting a JSON response, defaulting to a 500 status for unrecognized errors. Built-in exception classes like NotFoundException, BadRequestException, and ConflictException, all extending the base HttpException class, let you throw meaningful, correctly-coded errors directly from controllers or services with a single line of code. For applications needing more consistency or centralized control, custom exception filters, built using the @Catch() decorator and the ExceptionFilter interface, allow you to format every error response in a single, predictable shape, optionally adding logging or stripping sensitive details, and can be applied at the method, controller, or, most commonly, global level across the entire application.