Skip to Content

@TypedBody

@nestia/core
export function TypedBody(): ParameterDecorator;

Drop-in replacement for @Body() from @nestjs/common. Identical behavior plus:

  • The request body is validated at runtime against the parameter’s TypeScript type.
  • A type mismatch returns 400 Bad Request with a structured error payload — your handler never sees an invalid value.
  • Validation is up to 20,000× faster than class-validator.
  • The endpoint is visible to @nestia/sdk — body schema appears in the generated SDK, Swagger document, and e2e suite.

For non-JSON content types:


Basic usage

src/controllers/ArticlesController.ts
import { TypedBody, TypedRoute } from "@nestia/core"; import { Controller } from "@nestjs/common"; import { tags } from "typia"; interface IArticleCreate { title: string & tags.MinLength<3> & tags.MaxLength<50>; body: string; files: Array<{ name: string & tags.MinLength<1> & tags.MaxLength<255>; url: string & tags.Format<"url">; }>; } @Controller("articles") export class ArticlesController { @TypedRoute.Post() public async create( @TypedBody() input: IArticleCreate, ): Promise<{ id: string }> { // `input` is guaranteed to match IArticleCreate. // No defensive checks needed. return { id: "2b5e21d8-0e44-4482-bd3e-4540dee7f3d6" }; } }

That’s the whole change — @Body() becomes @TypedBody(). The type on the parameter does the rest.


What gets validated

Everything the type expresses:

  • Field presence and primitive types (string, number, boolean).
  • tags.* constraints — Format, MinLength, MaxLength, Pattern, Minimum, Maximum, MinItems, MaxItems, etc.
  • Nested objects and arrays of any depth.
  • Discriminated unions and recursive shapes.
  • Nullable (| null) and optional (?) fields.

If validation fails, the response body is:

HTTP 400
{ "statusCode": 400, "message": "Request body data is not following the promised type.", "errors": [ { "path": "$input.title", "expected": "string & MinLength<3>", "value": "no" } ] }

The errors array contains every failure with the path (dot / bracket notation rooted at $input), the expected type expression, and the offending value. Default mode is validate — every failure at once. See Configuration below to switch modes.

For the full tag catalog, see the typia tags reference .


When to use plain @Body instead

Mixing is fine. Reach for the vanilla decorator when:

  • You already wired up class-validator and want to migrate one route at a time.
  • The body is opaque to your handler (e.g. you forward it as-is to another service).
  • You’re A/B-testing two validator stacks.

Vanilla @Body() routes are invisible to @nestia/sdk — they don’t appear in the generated SDK, Swagger document, or e2e suite.


Combining with NestJS guards / pipes / interceptors

All NestJS features apply unchanged. The order is the standard NestJS pipeline:

  1. Guards (auth, ACL).
  2. Interceptors (logging, tracing).
  3. Pipes — Nestia validation happens here.
  4. Your handler — receives a validated input.
@UseGuards(JwtAuthGuard) @UseInterceptors(LoggingInterceptor) @TypedRoute.Post() public async create( @User() user: IAuthUser, @TypedBody() input: IArticleCreate, ): Promise<IArticle> { ... }

Validation failures throw BadRequestException, so global exception filters catch them like any other NestJS exception.


EncryptedBody

@EncryptedBody() accepts an AES-128/256 CBC encrypted body (PKCS #5 padding, Base64 encoded). Slower than @TypedBody() but adds transport-level confidentiality; the generated SDK encrypts requests automatically so consumers send plaintext at the call site.

Encrypted bodies do not appear in Swagger UI (the wire shape is opaque). Only the generated SDK can call them.


Configuration

No compilerOptions.plugins entry is needed for the default behavior. Without an explicit plugin config, @TypedBody uses "validate" and applies no LLM schema restriction.

Add a plugin entry only when you want to override those defaults:

tsconfig.json
{ "compilerOptions": { "strict": true, "plugins": [ { "transform": "typia/lib/transform", "enabled": false }, { "transform": "@nestia/core/native/transform.cjs", "validate": "validatePrune", "llm": { "strict": true } } ] } }

validate — which validator to use

ValueBehavior
"validate" (default)Returns every error in one response. Extra properties are ignored (pass through).
"validateEquals"Same as "validate" but also rejects extra properties.
"validatePrune"Same as "validate" but strips extra properties before your handler sees them.
"validateClone"Same as "validate" but deep-clones the input first (useful with mutable defaults).
"assert"Throws on the first error — fastest failure path. Single { path, expected, value }.
"assertEquals"Same as "assert" but also rejects extras.
"assertPrune"Same as "assert" but strips extras.
"assertClone"Same as "assert" but deep-clones.
"is"Boolean check only — no error detail. Use when you handle errors yourself.
"equals"Same as "is" but also rejects extras.

Choose by your front-end contract:

  • Form binding that highlights every bad field: "validate" (default).
  • Strict APIs that reject malformed payloads: "validateEquals".
  • Tolerant APIs that forward only known fields: "validatePrune".
  • Performance-critical fast-fail path: "assert".

llm — restrict to LLM-friendly schemas

"llm": true // forbid tuples "llm": { "strict": true } // also forbid additionalProperties and optional fields

When set, the transformer rejects any @TypedBody parameter whose type falls outside the schema subset accepted by LLM function calling. The check happens at build time. See Swagger → Chat for the LLM integration story.


Benchmarks

typia’s validators are up to 20,000× faster than class-validator on pathological cases (recursive unions, ultimate type shapes). Typical real-server speedup is 30–100×.

Why typia wins:

  • The validator is generated for your exact type at build time — no schema lookup, no Object.keys loop, no reflection.
  • The generated code is a flat boolean expression — modern JavaScript engines JIT this aggressively.

What also matters: typia handles types class-validator can’t — recursive unions, conditional types, template literals, discriminated unions with overlapping shapes. If your DTO got complex, this is where you feel it.


See also

Last updated on