@TypedException
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
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; // unknownSee 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:
| Value | Matches |
|---|---|
"2XX" | 200-299 |
"3XX" | 300-399 |
"4XX" | 400-499 |
"5XX" | 500-599 |
404 | exactly 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
@TypedRoutesuccess responses; error responses are honor-system on the server side. - Change runtime behavior. Remove every
@TypedExceptionand your routes still work — you just lose the schema annotations.
See also
- Swagger Document — how the schema reaches OpenAPI.
- SDK Propagation Mode — typed error branches in the generated client.