Skip to Content

@TypedException

@nestia/core
export function TypedException<T extends object>( status: number | "2XX" | "3XX" | "4XX" | "5XX", description?: string, ): MethodDecorator; export function TypedException<T extends object>(props: { status: number | "2XX" | "3XX" | "4XX" | "5XX"; description?: string; example?: T; examples?: Record<string, T>; }): MethodDecorator;

Declare an error response a route can return — typed, documented, optionally exampled.

The decorator does not affect runtime behavior — your handler still throws / returns whatever it normally would. It tells the Swagger generator and the SDK what shape that error has, so the OpenAPI document and the propagation-mode SDK both pick it up with full type information.

When you’d reach for this: any route where 4xx / 5xx responses carry a structured body that the client cares about. Validation errors are auto-documented by @TypedBody, so you don’t need it for those — but business errors ("order locked", "insufficient funds") deserve a @TypedException.


Basic usage

src/controllers/OrdersController.ts
import { TypedBody, TypedException, TypedRoute } from "@nestia/core"; import { Controller } from "@nestjs/common"; interface IOrderNotFound { code: "ORDER_NOT_FOUND"; orderId: string; } interface IInsufficientFunds { code: "INSUFFICIENT_FUNDS"; balance: number; required: number; } interface IInternalServerError { code: "INTERNAL"; traceId: string; } @Controller("orders") export class OrdersController { @TypedRoute.Post(":id/pay") @TypedException<IOrderNotFound>(404, "Order does not exist") @TypedException<IInsufficientFunds>(402, "Balance too low") @TypedException<IInternalServerError>("5XX", "Unhandled error") public async pay(...): Promise<IReceipt> { ... } }

The handler throws regular NotFoundException / ConflictException / etc. with the body shape that matches each @TypedException. The decorators annotate those shapes for downstream tooling.


What it changes

In the Swagger document

Each @TypedException becomes an entry under the route’s responses map, with the declared schema, description, and any examples.

responses: "404": description: "Order does not exist" content: application/json: schema: { $ref: "#/components/schemas/IOrderNotFound" }

In the generated SDK (propagation mode)

When you set propagate: true in nestia.config.ts, the SDK function returns a discriminated union that includes branches for each @TypedException status. The client can branch on the status code with type narrowing:

const out = await api.functional.orders.pay(connection, id, input); if (out.success) out.data.receiptId; // IReceipt else if (out.status === 404) out.data.orderId; // IOrderNotFound else if (out.status === 402) out.data.balance; // IInsufficientFunds else /* 5XX or other */ out.data; // unknown

See SDK → Propagation Mode for the full pattern.


With examples

The object form takes example (one) or examples (a map of named samples):

@TypedException<IBadRequest>({ status: 400, description: "Invalid payload", example: { code: "INVALID_TITLE", message: "Title must be 3-50 characters", }, })

Examples appear in Swagger UI as preset payloads.


Range matchers

The status accepts ranges as strings:

ValueMatches
"2XX"200-299
"3XX"300-399
"4XX"400-499
"5XX"500-599
404exactly 404

Use ranges for catch-all schemas ("5XX" for “any server error”). Specific codes win over ranges when both are declared.


What it does not do

  • Throw the exception for you. That’s still your handler’s job (via throw new HttpException(...) or returning a NestJS exception).
  • Validate the response body. If you throw a body that doesn’t match the declared type, the client sees what you sent, not what you declared. Nestia validates @TypedRoute success responses; error responses are honor-system on the server side.
  • Change runtime behavior. Remove every @TypedException and your routes still work — you just lose the schema annotations.

See also

Last updated on