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.
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.
statusCodeandpathalways present for log correlation.- No stack traces in production — log them server-side, surface a
requestIdto the client. - Map domain errors near the boundary, not deep in business logic.