# DTOs and validation

> Source: https://learn-nestjs.com/dto-and-validation/
> Part of Learn NestJS, free to read.

A DTO — data transfer object — is a class describing the shape of a request. Decorate it, register the global `ValidationPipe`, and Nest rejects anything that does not match before your controller runs.

```ts src/users/dto/create-user.dto.ts
import { IsEmail, IsInt, IsOptional, IsString, Length, Max, Min } from 'class-validator';

export class CreateUserDto {
  @IsEmail()
  email!: string;

  @IsString()
  @Length(2, 60)
  name!: string;

  @IsOptional()
  @IsInt()
  @Min(13)
  @Max(130)
  age?: number;
}
```

```ts src/main.ts
app.useGlobalPipes(new ValidationPipe({
  whitelist: true,             // strip properties with no decorator
  forbidNonWhitelisted: true,  // ...or reject the request outright
  transform: true,             // instantiate the DTO class, coerce types
  transformOptions: { enableImplicitConversion: true },
}));
```

## Why `whitelist` is the important one

Without it, a client can send fields you never declared and they arrive in your DTO. If any code path spreads that object into a database update, you have mass assignment:

```ts
// without whitelist, this is a privilege escalation
await this.prisma.user.update({ where: { id }, data: { ...dto } });
// client sent { name: "x", role: "admin" }
```

`whitelist: true` strips undeclared properties. `forbidNonWhitelisted: true` rejects the request instead, which is better in an internal API where a surprise field means a client bug.

:::warn This is the single most valuable line in your bootstrap
Generated NestJS code frequently registers `ValidationPipe` with no options, which validates the fields you declared and silently passes through everything else. Always pass `whitelist: true`.
:::

## Transformation

With `transform: true`, Nest instantiates the DTO class rather than handing you a plain object. That means `instanceof` works, defaults apply, and `class-transformer` decorators run.

```ts
export class ListUsersDto {
  @Type(() => Number)          // query params are always strings
  @IsInt() @Min(1) @Max(100)
  limit = 20;

  @Transform(({ value }) => value?.trim().toLowerCase())
  @IsOptional() @IsString()
  search?: string;
}
```

## Nested objects and arrays

Nested validation does not recurse unless you ask.

```ts
export class CreateOrderDto {
  @ValidateNested({ each: true })
  @Type(() => OrderLineDto)     // required, or nested validation silently passes
  @ArrayMinSize(1)
  lines!: OrderLineDto[];
}
```

Omitting `@Type()` is the most common validation bug in Nest: the array is accepted, the items are never checked.

## Partial updates

```ts
import { PartialType } from '@nestjs/mapped-types';

export class UpdateUserDto extends PartialType(CreateUserDto) {}
```

Every field becomes optional, decorators are preserved. `PickType`, `OmitType` and `IntersectionType` compose similarly.

## Response shaping

Validation guards the way in. `class-transformer` guards the way out.

```ts src/users/entities/user.entity.ts
export class UserEntity {
  id!: string;
  email!: string;

  @Exclude()
  passwordHash!: string;
}
```

```ts
@UseInterceptors(ClassSerializerInterceptor)
@Controller('users')
export class UsersController {}
```

Now `passwordHash` cannot leak, even if a service returns the raw database row. Worth doing on every entity that has a secret in it — it converts a class of accidental disclosure into an impossibility.

## An alternative: schema-first

If you prefer schemas to decorators, `nestjs-zod` gives you a Zod schema as the single source of truth for both runtime validation and the TypeScript type, which removes the drift between the two.

```ts
const CreateUser = z.object({ email: z.email(), name: z.string().min(2) });
export class CreateUserDto extends createZodDto(CreateUser) {}
```

## Common questions

### Do I need DTOs if I already have TypeScript types?

Yes. Types are erased at compile time — they constrain your code, not the incoming JSON. A DTO with validators is the only thing actually checking what arrived over the wire.

### Where do business rules go?

In the service. The DTO checks shape and format ("is this a valid email"); the service checks state ("is this email already registered"), because that needs the database.

### Why is my nested array not being validated?

Almost certainly a missing `@Type(() => Child)` alongside `@ValidateNested({ each: true })`. Without it, `class-transformer` does not know what class to instantiate and validation of the items is skipped silently.
