Built-in HTTP Exceptions
NestJS ships a family of ready-made exception classes that map directly onto standard HTTP status codes. Instead of manually constructing a response with the right code and shape, you simply throw new NotFoundException() from anywhere in your request pipeline, and the framework’s default exception filter serializes it into a clean JSON error with the matching status. This keeps controllers and services focused on business rules while guaranteeing consistent, well-formed error responses. This page catalogs the built-in classes, the base HttpException they extend, and how to customize messages and attach a cause.
The base HttpException
Every built-in exception derives from HttpException, exported from @nestjs/common. Its constructor takes a response (the body, either a string or an object) and a numeric status code. The named subclasses simply call super(...) with the correct status, so you rarely need HttpException directly — but it is there when you want a code that has no dedicated class.
import { HttpException, HttpStatus } from '@nestjs/common';
throw new HttpException('Forbidden resource', HttpStatus.FORBIDDEN);
// Or with a fully custom body:
throw new HttpException(
{ statusCode: HttpStatus.I_AM_A_TEAPOT, message: 'No coffee here', error: 'Teapot' },
HttpStatus.I_AM_A_TEAPOT,
);
When you pass a string, Nest wraps it into a standard object body. When you pass an object, that object becomes the response body verbatim, giving you full control over the shape.
The built-in exception classes
Each class maps to one status code and lives in @nestjs/common. The most commonly used are listed below.
| Exception class | Status | Code |
|---|---|---|
BadRequestException | Bad Request | 400 |
UnauthorizedException | Unauthorized | 401 |
ForbiddenException | Forbidden | 403 |
NotFoundException | Not Found | 404 |
MethodNotAllowedException | Method Not Allowed | 405 |
NotAcceptableException | Not Acceptable | 406 |
RequestTimeoutException | Request Timeout | 408 |
ConflictException | Conflict | 409 |
GoneException | Gone | 410 |
PayloadTooLargeException | Payload Too Large | 413 |
UnsupportedMediaTypeException | Unsupported Media Type | 415 |
UnprocessableEntityException | Unprocessable Entity | 422 |
InternalServerErrorException | Internal Server Error | 500 |
NotImplementedException | Not Implemented | 501 |
BadGatewayException | Bad Gateway | 502 |
ServiceUnavailableException | Service Unavailable | 503 |
GatewayTimeoutException | Gateway Timeout | 504 |
Throwing from a controller or service
You can throw these anywhere — guards, pipes, interceptors, services, or controllers. The default filter catches the exception and converts it into the response. Below, a service raises NotFoundException when a lookup misses; the controller stays clean.
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
@Injectable()
export class UsersService {
private readonly users = new Map<number, { id: number; email: string }>();
findOne(id: number) {
const user = this.users.get(id);
if (!user) {
throw new NotFoundException(`User ${id} not found`);
}
return user;
}
create(email: string) {
const exists = [...this.users.values()].some((u) => u.email === email);
if (exists) {
throw new ConflictException('Email already registered');
}
const id = this.users.size + 1;
const user = { id, email };
this.users.set(id, user);
return user;
}
}
A request for a missing user produces a predictable JSON body.
Output:
GET /users/99
404 { "statusCode": 404, "message": "User 99 not found", "error": "Not Found" }
If you omit the message argument, Nest fills in a sensible default derived from the status — NotFoundException yields "message": "Not Found".
Customizing messages and the response body
Each built-in class accepts two optional arguments: a string | object description and an options object. Passing a string overrides the message field; passing an object replaces the entire body.
import { BadRequestException } from '@nestjs/common';
// Override just the message:
throw new BadRequestException('Quantity must be positive');
// Replace the whole body with a custom structure:
throw new BadRequestException({
statusCode: 400,
message: ['quantity must be a positive number'],
error: 'Validation Failed',
field: 'quantity',
});
Output:
400 {
"statusCode": 400,
"message": ["quantity must be a positive number"],
"error": "Validation Failed",
"field": "quantity"
}
Returning an array for
messageis exactly what Nest’s built-inValidationPipedoes, which lets clients render one error per failed field. Following that convention keeps your manual errors consistent with framework-generated ones.
Attaching a cause for logging
The second argument carries an options object whose cause property preserves the original error. The cause is not serialized into the HTTP response, but it is attached to the thrown error object so logging filters and observability tooling can inspect the root failure.
import { InternalServerErrorException } from '@nestjs/common';
try {
await this.db.query('SELECT 1');
} catch (err) {
throw new InternalServerErrorException('Database unavailable', {
cause: err,
description: 'Connection pool exhausted',
});
}
The client still receives a generic 500, while your logs retain the underlying stack trace via error.cause. This separation keeps internal details out of the response without losing them.
Never leak raw infrastructure errors to the client. Pass the original error as
causefor your logs and return a safe, generic message in the body.
Best Practices
- Prefer the named subclass (
NotFoundException) overnew HttpException(..., 404)— it reads clearly and is self-documenting. - Throw exceptions from services, not just controllers; the framework propagates them up the pipeline regardless of where they originate.
- Use
HttpStatusenum constants instead of magic numbers when you do reach for the baseHttpException. - Keep client-facing messages safe and generic for
5xxerrors; attach the real error viacausefor logging only. - Match the
message-as-array convention fromValidationPipewhen reporting multiple field errors, so clients can rely on a single shape. - For domain-specific errors that recur across your app, wrap a built-in exception in a custom class to centralize the message and status.