W Imperium Romanum nie każda brama ma te same zasady. Brama handlowa przepuszcza kupców, brama wojskowa — legionistów, a brama senatu — tylko senatorów. W class-validator mamy podobny mechanizm: grupy walidacji (validation groups).
Typowy przypadek: podczas tworzenia nowego legionisty wszystkie pola są wymagane, ale przy aktualizacji chcemy pozwolić na przesłanie tylko wybranych pól:
1// CREATE: wszystko wymagane
2// { name: "Marcus", rank: "miles", age: 25, legio: "X" }
3
4// UPDATE: tylko zmieniane pola
5// { rank: "centurio" } ← tylko awansKażdy dekorator walidacji przyjmuje opcję
groups:1import {
2 IsString,
3 IsNotEmpty,
4 IsNumber,
5 Min,
6 IsOptional,
7} from 'class-validator';
8
9export class LegionaryDto {
10 @IsString({ groups: ['create', 'update'] })
11 @IsNotEmpty({ groups: ['create'] }) // Wymagane tylko przy tworzeniu
12 name: string;
13
14 @IsString({ groups: ['create', 'update'] })
15 @IsNotEmpty({ groups: ['create'] })
16 rank: string;
17
18 @IsNumber({}, { groups: ['create', 'update'] })
19 @Min(16, { groups: ['create', 'update'] })
20 @IsNotEmpty({ groups: ['create'] })
21 age: number;
22
23 @IsString({ groups: ['create', 'update'] })
24 @IsNotEmpty({ groups: ['create'] })
25 legio: string;
26}Aby grupy zadziałały, musisz je aktywować w ValidationPipe:
1// Endpoint tworzenia — aktywuj grupę 'create'
2@Post()
3@UsePipes(new ValidationPipe({ groups: ['create'] }))
4createLegionary(@Body() dto: LegionaryDto) {
5 return this.service.create(dto);
6}
7
8// Endpoint aktualizacji — aktywuj grupę 'update'
9@Patch(':id')
10@UsePipes(new ValidationPipe({ groups: ['update'] }))
11updateLegionary(
12 @Param('id') id: string,
13 @Body() dto: LegionaryDto,
14) {
15 return this.service.update(id, dto);
16}Częsty wzorzec — pole opcjonalne tylko przy aktualizacji:
1export class LegionaryDto {
2 @IsOptional({ groups: ['update'] })
3 @IsString({ groups: ['create', 'update'] })
4 @IsNotEmpty({ groups: ['create'] })
5 name: string;
6}Przy
create pole name jest wymagane i musi być stringiem. Przy update pole name jest opcjonalne, ale jeśli jest podane — musi być stringiem.W praktyce wielu programistów zamiast grup używa osobnych klas DTO z dziedziczeniem:
1// Base DTO ze wspólnymi dekoratorami
2export class CreateLegionaryDto {
3 @IsString()
4 @IsNotEmpty()
5 name: string;
6
7 @IsString()
8 @IsNotEmpty()
9 rank: string;
10
11 @IsNumber()
12 @Min(16)
13 age: number;
14}
15
16// Update DTO - wszystkie pola opcjonalne
17export class UpdateLegionaryDto {
18 @IsOptional()
19 @IsString()
20 name?: string;
21
22 @IsOptional()
23 @IsString()
24 rank?: string;
25
26 @IsOptional()
27 @IsNumber()
28 @Min(16)
29 age?: number;
30}NestJS oferuje też specjalny helper
PartialType:1import { PartialType } from '@nestjs/mapped-types';
2
3export class UpdateLegionaryDto extends PartialType(CreateLegionaryDto) {}
4// Automatycznie tworzy kopię z wszystkimi polami opcjonalnymi!NestJS oferuje kilka pomocników do tworzenia DTO na bazie istniejących:
1import { PartialType, PickType, OmitType, IntersectionType } from '@nestjs/mapped-types';
2
3// Wszystkie pola opcjonalne
4class UpdateDto extends PartialType(CreateDto) {}
5
6// Tylko wybrane pola
7class LoginDto extends PickType(CreateDto, ['email', 'password']) {}
8
9// Bez wybranych pól
10class PublicDto extends OmitType(CreateDto, ['password', 'secret']) {}
11
12// Połączenie dwóch DTO
13class FullDto extends IntersectionType(CreateDto, MetadataDto) {}Grupy walidacji i mapped types dają ci elastyczność w obsłudze różnych scenariuszy. Każda brama imperium może teraz mieć własne, dostosowane reguły!