Skip to main content
nestrs ships a #[dto] proc-macro that turns a plain Rust struct into a fully-typed, validated Data Transfer Object. It derives Deserialize, Serialize, Validate (from the validator crate), and NestDto — and it enables a set of NestJS-style field attributes (#[IsEmail], #[Length], etc.) so your validation intent is visible at the declaration site rather than buried in implementation code.

Defining a DTO

Annotate a struct with #[dto] and add validation attributes to each field:
#[dto] expands to a struct with #[serde(deny_unknown_fields)] by default. Any JSON body that contains a key not declared on the struct returns a 400 Bad Request before your handler runs.
#[IsString] is a readability marker only — Rust’s type system already enforces that a String field is a string. It compiles to no validation code.

Available validation attributes

You can also use raw validator attributes directly: #[validate(range(min = 0))] works on any #[dto] field.

Using ValidatedBody in a controller

Pair ValidatedBody<T> with your DTO type as an Axum extractor. nestrs validates the deserialized value before your handler body runs and returns 422 Unprocessable Entity on failure:

ValidatedPath and ValidatedQuery

The same pattern applies to path parameters and query strings:

Nested DTOs with ValidateNested

Use #[ValidateNested] on a field whose type is itself a #[dto] struct to trigger recursive validation:
Recursive validation only fires if the nested struct also derives Validate. #[dto] handles this automatically — but if you hand-write a nested struct, make sure it derives validator::Validate.

Using ValidationPipe explicitly

ValidatedBody, ValidatedPath, and ValidatedQuery run validation inline as part of extraction. If you prefer the NestJS #[use_pipes(ValidationPipe)] style you can use it with #[param::body] / #[param::query] / #[param::param]:
Both styles produce the same 422 response shape when validation fails.

Allowing unknown fields

By default #[dto] adds #[serde(deny_unknown_fields)] so clients cannot send undocumented keys. To opt out:
Keep deny_unknown_fields enabled (the default) for public-facing APIs to prevent clients from smuggling extra data and to catch typos in field names early.

Error response shape

A failed validation returns 422 Unprocessable Entity with a JSON body: