Skip to main content
nestrs DTO validation combines serde for deserialization with the validator crate for constraint checking. The #[dto] macro derives both automatically; ValidationPipe or ValidatedBody<T> runs the validation before your handler receives the data. Invalid payloads return 422 Unprocessable Entity with a structured error body.

#[dto]

Derives serde::Deserialize, validator::Validate, and NestDto on a struct. By default it also emits #[serde(deny_unknown_fields)] so any JSON key not in the struct definition causes a 400 Bad Request (deserialization fails inside the JSON extractor, before validation runs).

#[dto(allow_unknown_fields)]

Opts out of deny_unknown_fields. Use this when you intentionally accept JSON payloads from forward-compatible clients that may include extra fields.
Do not manually add #[serde(deny_unknown_fields)] to a struct that already uses #[dto]—the macro applies it by default and duplicating the attribute causes a compile error.

NestDto trait

NestDto is a marker trait generated by #[dto]. The validation extractors (ValidatedBody<T>, ValidatedQuery<T>, ValidatedPath<T>) and ValidationPipe do not require it — they accept any T that is DeserializeOwned (or Validate for the pipe), so hand-written structs work too. NestDto marks #[dto]-derived types for macros that require them, such as #[crud], which asserts at compile time that its generated DTOs came from #[dto].
You do not implement NestDto manually; #[dto] handles it.

Using DTOs in handlers

Field validation attributes

All field attributes are applied inside a #[dto] struct. They expand to validator crate constraint annotations.

String constraints

attribute
Readability marker — string-ness is enforced by the Rust field type. On a String (or Option<String>) field it emits no validation code; on any other field type it is a compile-time error. Use #[IsNotEmpty] for a non-empty/presence check.
attribute
Validates that the field is a well-formed email address.
attribute
Validates that the field is a well-formed URL.
attribute
Validates string length. Both min and max are optional.

Numeric constraints

attribute
Compile-checked: the field must be an integer type (i8–i128, u8–u128, isize, usize). Adds no runtime validation — the field type already guarantees it; a non-integer field is a compile-time error.
attribute
Compile-checked: the field must be a numeric type (integers, or f32/f64). Adds no runtime validation; a non-numeric field is a compile-time error.
attribute
Validates that the numeric field is greater than or equal to N.
attribute
Validates that the numeric field is less than or equal to N.

Boolean and optional

attribute
Compile-checked: the field must be a bool. Adds no runtime validation; a non-bool field is a compile-time error.
attribute
Stripped at compile time — optionality comes entirely from the field type. Wrap the field in Option<T> and a missing JSON key deserializes as None, skipping the field’s other validators. The attribute exists to document intent for readers coming from NestJS’s @IsOptional().

Nested DTOs

attribute
Runs validation recursively on a nested struct field. The nested type must also derive Validate (which #[dto] provides).

Comprehensive example

The following DTO covers string, numeric, boolean, optional, and nested validation in a single type:

deny_unknown_fields default behavior

#[dto] applies #[serde(deny_unknown_fields)] by default. This means any JSON key in the request body that is not a field on the struct causes deserialization to fail with a 400 Bad Request before validation even runs.
This is intentional and matches NestJS’s ValidationPipe behavior with whitelist: true, forbidNonWhitelisted: true (unknown keys are rejected, not silently stripped). Use #[dto(allow_unknown_fields)] when you need to accept extra fields from clients on a different schema version.