Skip to content
NestJS ns exceptions 4 min read

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 classStatusCode
BadRequestExceptionBad Request400
UnauthorizedExceptionUnauthorized401
ForbiddenExceptionForbidden403
NotFoundExceptionNot Found404
MethodNotAllowedExceptionMethod Not Allowed405
NotAcceptableExceptionNot Acceptable406
RequestTimeoutExceptionRequest Timeout408
ConflictExceptionConflict409
GoneExceptionGone410
PayloadTooLargeExceptionPayload Too Large413
UnsupportedMediaTypeExceptionUnsupported Media Type415
UnprocessableEntityExceptionUnprocessable Entity422
InternalServerErrorExceptionInternal Server Error500
NotImplementedExceptionNot Implemented501
BadGatewayExceptionBad Gateway502
ServiceUnavailableExceptionService Unavailable503
GatewayTimeoutExceptionGateway Timeout504

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 message is exactly what Nest’s built-in ValidationPipe does, 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 cause for your logs and return a safe, generic message in the body.

Best Practices

  • Prefer the named subclass (NotFoundException) over new 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 HttpStatus enum constants instead of magic numbers when you do reach for the base HttpException.
  • Keep client-facing messages safe and generic for 5xx errors; attach the real error via cause for logging only.
  • Match the message-as-array convention from ValidationPipe when 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.
Last updated June 14, 2026
Was this helpful?