> ## Documentation Index
> Fetch the complete documentation index at: https://nestrs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# DTO validation macros in nestrs

> Reference for #[dto], the NestDto trait, and all field validation attributes including IsEmail, Length, Min, Max, ValidateNested, and more.

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 422 error.

```rust theme={null}
use nestrs::prelude::*;

#[dto]
struct CreateUserDto {
    #[IsEmail]
    email: String,

    #[Length(min = 3, max = 32)]
    username: String,

    #[IsOptional]
    #[IsInt]
    age: Option<i64>,
}
```

### `#[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.

```rust theme={null}
#[dto(allow_unknown_fields)]
struct PartialUpdateDto {
    #[IsString]
    name: Option<String>,
}
```

<Warning>
  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.
</Warning>

## `NestDto` trait

`NestDto` is a marker trait generated by `#[dto]`. It is used by `ValidationPipe` and the `ValidatedBody<T>`, `ValidatedQuery<T>`, and `ValidatedPath<T>` extractors to enforce that only DTOs marked with `#[dto]` are passed through the validation pipeline.

```rust theme={null}
pub trait NestDto {}
```

You do not implement `NestDto` manually; `#[dto]` handles it.

## Using DTOs in handlers

<Tabs>
  <Tab title="ValidatedBody extractor">
    ```rust theme={null}
    use nestrs::prelude::*;

    #[dto]
    struct SignupDto {
        #[IsEmail]
        email: String,
    }

    #[routes(state = AppState)]
    impl AuthController {
        #[post("/signup")]
        async fn signup(
            ValidatedBody(dto): ValidatedBody<SignupDto>,
        ) -> &'static str {
            let _ = dto;
            "created"
        }
    }
    ```
  </Tab>

  <Tab title="#[param::body] + ValidationPipe">
    ```rust theme={null}
    use nestrs::prelude::*;

    #[dto]
    struct SignupDto {
        #[IsEmail]
        email: String,
    }

    #[routes(state = AppState)]
    impl AuthController {
        #[post("/signup")]
        #[use_pipes(ValidationPipe)]
        async fn signup(#[param::body] dto: SignupDto) -> &'static str {
            let _ = dto;
            "created"
        }
    }
    ```
  </Tab>

  <Tab title="ValidatedQuery extractor">
    ```rust theme={null}
    use nestrs::prelude::*;

    #[dto]
    struct PaginationQuery {
        #[IsInt]
        #[Min(1)]
        page: i64,

        #[IsInt]
        #[Max(100)]
        limit: i64,
    }

    #[routes(state = AppState)]
    impl ItemsController {
        #[get("/")]
        async fn list(
            ValidatedQuery(q): ValidatedQuery<PaginationQuery>,
        ) -> axum::Json<serde_json::Value> {
            axum::Json(serde_json::json!({ "page": q.page, "limit": q.limit }))
        }
    }
    ```
  </Tab>
</Tabs>

## Field validation attributes

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

### String constraints

<ResponseField name="#[IsString]" type="attribute">
  Validates that the field is a non-empty string. Useful as a presence check when used with `String`.
</ResponseField>

<ResponseField name="#[IsEmail]" type="attribute">
  Validates that the field is a well-formed email address.
</ResponseField>

<ResponseField name="#[IsUrl]" type="attribute">
  Validates that the field is a well-formed URL.
</ResponseField>

<ResponseField name="#[Length(min = N, max = N)]" type="attribute">
  Validates string length. Both `min` and `max` are optional.
</ResponseField>

```rust theme={null}
#[dto]
struct ProfileDto {
    #[IsEmail]
    email: String,

    #[IsUrl]
    website: String,

    #[Length(min = 1, max = 255)]
    bio: String,
}
```

### Numeric constraints

<ResponseField name="#[IsInt]" type="attribute">
  Validates that the field is an integer type (`i8`–`i128`, `u8`–`u128`, `isize`, `usize`).
</ResponseField>

<ResponseField name="#[IsNumber]" type="attribute">
  Validates that the field is a numeric type (also accepts `f32`, `f64`).
</ResponseField>

<ResponseField name="#[Min(N)]" type="attribute">
  Validates that the numeric field is greater than or equal to `N`.
</ResponseField>

<ResponseField name="#[Max(N)]" type="attribute">
  Validates that the numeric field is less than or equal to `N`.
</ResponseField>

```rust theme={null}
#[dto]
struct PriceDto {
    #[IsNumber]
    #[Min(0)]
    amount: f64,

    #[IsInt]
    #[Min(1)]
    #[Max(1000)]
    quantity: i64,
}
```

### Boolean and optional

<ResponseField name="#[IsBoolean]" type="attribute">
  Validates that the field is a `bool`.
</ResponseField>

<ResponseField name="#[IsOptional]" type="attribute">
  Marks the field as optional in the validation pipeline. Wrap the field type in `Option<T>` and add this attribute so that a missing JSON key is accepted without triggering other validators on the field.
</ResponseField>

```rust theme={null}
#[dto]
struct UpdateDto {
    #[IsOptional]
    #[IsString]
    name: Option<String>,

    #[IsOptional]
    #[IsBoolean]
    active: Option<bool>,
}
```

### Nested DTOs

<ResponseField name="#[ValidateNested]" type="attribute">
  Runs validation recursively on a nested struct field. The nested type must also derive `Validate` (which `#[dto]` provides).
</ResponseField>

```rust theme={null}
#[dto]
struct AddressDto {
    #[IsString]
    street: String,

    #[IsString]
    city: String,
}

#[dto]
struct OrderDto {
    #[IsString]
    product_id: String,

    #[ValidateNested]
    shipping_address: AddressDto,
}
```

## Comprehensive example

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

```rust theme={null}
use nestrs::prelude::*;

#[dto]
struct ContactInfoDto {
    #[IsString]
    first_name: String,

    #[IsString]
    last_name: String,

    #[IsEmail]
    email: String,

    #[IsOptional]
    #[IsUrl]
    website: Option<String>,

    #[IsInt]
    #[Min(18)]
    #[Max(120)]
    age: i64,

    #[IsOptional]
    #[IsBoolean]
    newsletter: Option<bool>,
}

#[dto]
struct CreateContactDto {
    #[ValidateNested]
    contact: ContactInfoDto,

    #[IsOptional]
    #[Length(max = 500)]
    notes: Option<String>,
}

#[controller(prefix = "/contacts")]
struct ContactsController;

#[routes(state = AppState)]
impl ContactsController {
    #[post("/")]
    #[http_code(201)]
    async fn create(
        ValidatedBody(dto): ValidatedBody<CreateContactDto>,
    ) -> axum::Json<serde_json::Value> {
        axum::Json(serde_json::json!({ "created": true }))
    }
}
```

## `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 422 response before validation even runs.

```json theme={null}
// Request body — fails with 422 if `#[dto]` is used (no allow_unknown_fields)
{
  "email": "user@example.com",
  "unexpected_field": "value"
}
```

This is intentional and matches NestJS's `ValidationPipe` behavior with `whitelist: true`. Use `#[dto(allow_unknown_fields)]` when you need to accept extra fields from clients on a different schema version.
