Exception Filters

Catch Errors & Shape the Response

Exception Filters

Exception filters handle thrown errors. Nest has good defaults; override them when you need a custom error format or richer logging.

4 min read Level 2/5 #nestjs#errors#exceptions
What you'll learn
  • Throw built-in HttpExceptions for clean status codes
  • Write a @Catch filter that handles specific error types
  • Apply a global filter for a uniform error envelope

When a handler throws, Nest’s exception layer catches it, picks a status code, and shapes a JSON response. The defaults are sensible; filters let you customize.

Throwing HttpExceptions

The simplest “filter” is just throwing the right error. Nest provides one class per common status code.

import {
  BadRequestException, NotFoundException, ConflictException,
} from '@nestjs/common';

async findOne(id: string) {
  const user = await this.repo.findById(id);
  if (!user) throw new NotFoundException(`user ${id} not found`);
  return user;
}

async create(dto: CreateUserDto) {
  if (await this.repo.findByEmail(dto.email)) {
    throw new ConflictException('email already in use');
  }
  return this.repo.save(dto);
}

The full set includes BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException, ConflictException, UnprocessableEntityException, and InternalServerErrorException. Each maps to its HTTP status.

Writing a Custom Filter

Implement ExceptionFilter and use @Catch() to declare which exceptions the filter handles. Leave the parens empty to catch everything.

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

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const res = ctx.getResponse();
    const req = ctx.getRequest();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    res.status(status).json({
      statusCode: status,
      path: req.url,
      timestamp: new Date().toISOString(),
      message:
        exception instanceof HttpException
          ? exception.getResponse()
          : 'internal server error',
    });
  }
}

Wire it globally so every unhandled error follows the format:

// main.ts
app.useGlobalFilters(new AllExceptionsFilter());

Catching Specific Errors

Pass exception classes to @Catch() to narrow scope. Useful for translating domain-specific errors into HTTP responses.

@Catch(PrismaClientKnownRequestError)
export class PrismaExceptionFilter implements ExceptionFilter {
  catch(err: PrismaClientKnownRequestError, host: ArgumentsHost) {
    const res = host.switchToHttp().getResponse();
    if (err.code === 'P2002') {
      return res.status(409).json({ message: 'unique constraint failed' });
    }
    return res.status(500).json({ message: 'database error' });
  }
}

Apply it on a controller, method, or globally — same options as guards and interceptors.

Filter Order

If multiple filters could handle an exception, the most specific controller-level filter wins, falling back to module-level then global. That lets a feature override the global format for its own routes.

What Makes a Good Error Response

A few practices worth adopting early:

  • One envelope shape across the whole API.
  • statusCode and path always present for log correlation.
  • No stack traces in production — log them server-side, surface a requestId to the client.
  • Map domain errors near the boundary, not deep in business logic.
ExecutionContext & Reflector →