В библиотеке class-validator группы валидации представляют собой механизм условного применения правил в зависимости от сценария использования одной и той же модели данных. Это позволяет разделять проверки для создания, обновления, частичных изменений и любых других бизнес-контекстов без необходимости дублировать классы или усложнять структуру DTO.
Группы задаются строковыми идентификаторами и связываются с
конкретными валидаторами через параметр groups, а затем
активируются при выполнении функции validate или
validateOrReject.
Каждый декоратор в Class-validator может принимать объект
конфигурации, содержащий поле groups:
import { IsString, IsNotEmpty, IsEmail } from "class-validator";
export class UserDto {
@IsString({ groups: ["create", "update"] })
@IsNotEmpty({ groups: ["create"] })
name: string;
@IsEmail({}, { groups: ["create"] })
email: string;
}
В данном примере:
name обязателен только при создании
(create)update) допускается отсутствие
значенияemail проверяется только в сценарии созданияТакой подход позволяет одной моделью описывать разные контексты валидации.
Чтобы группы начали работать, их необходимо передать в параметры функции валидации:
import { validate } from "class-validator";
const dto = new UserDto();
dto.name = "";
validate(dto, { groups: ["create"] }).then(errors => {
console.log(errors);
});
Если группы не переданы, применяется поведение по умолчанию —
используются только валидаторы без указанного groups.
Декораторы, не содержащие параметр groups, считаются
принадлежащими группе по умолчанию.
import { IsString } from "class-validator";
class ProductDto {
@IsString()
title: string;
}
В этом случае:
title будет валидироваться всегда, независимо от
переданных группЕсли же валидация вызывается с конкретными группами, поведение по умолчанию зависит от конфигурации и наличия других правил.
Один и тот же валидатор может принадлежать нескольким группам одновременно:
import { IsOptional, IsNumber, Min } from "class-validator";
class OrderDto {
@IsNumber({}, { groups: ["create", "update"] })
@Min(1, { groups: ["create"] })
quantity: number;
}
Логика применения:
create активны оба ограниченияupdate применяется только проверка типаЭто позволяет гибко управлять строгими и мягкими сценариями валидации.
import { IsString, IsEmail, IsNotEmpty } from "class-validator";
class RegisterUserDto {
@IsString({ groups: ["register"] })
@IsNotEmpty({ groups: ["register"] })
username: string;
@IsEmail({}, { groups: ["register"] })
email: string;
}
validate(dto, { groups: ["register"] });
import { IsOptional, IsString } from "class-validator";
class UpdateUserDto {
@IsOptional({ groups: ["update"] })
@IsString({ groups: ["update"] })
username?: string;
}
Здесь каждое поле становится необязательным только в рамках группы
update.
Особенность сочетания IsOptional и групп часто вызывает
ошибки проектирования. Важно учитывать, что:
IsOptional влияет только на наличие значенияКорректный паттерн:
import { IsOptional, IsString } from "class-validator";
class ProfileDto {
@IsOptional({ groups: ["update"] })
@IsString({ groups: ["update"] })
bio?: string;
}
В этом случае:
updateГруппы позволяют строить многоуровневую валидацию одного и того же поля:
import { Length, Matches } from "class-validator";
class PasswordDto {
@Length(8, 20, { groups: ["register", "change-password"] })
password: string;
@Matches(/[A-Z]/, { groups: ["register"] })
uppercaseRequirement: string;
@Matches(/[0-9]/, { groups: ["register", "change-password"] })
numberRequirement: string;
}
Здесь:
При наследовании классов группы не объединяются автоматически — они сохраняются на уровне декораторов.
class BaseDto {
@IsString({ groups: ["base"] })
id: string;
}
class ExtendedDto extends BaseDto {
@IsString({ groups: ["extended"] })
extra: string;
}
При валидации:
base влияет только на idextended влияет только на
extraКомбинирование групп требует явного указания:
validate(dto, { groups: ["base", "extended"] });
Если ни один декоратор не соответствует переданным группам, поле полностью исключается из проверки.
validate(dto, { groups: ["unknown"] });
Результат:
Это поведение важно учитывать при динамическом формировании групп.
На уровне архитектуры часто выделяются стандартные наборы:
create — строгая валидация всех обязательных полейupdate — частичная валидацияdelete — проверка идентификаторовauth — сценарии авторизацииinternal — системные операцииПример унифицированной модели:
class AccountDto {
@IsString({ groups: ["create", "update", "auth"] })
login: string;
@IsString({ groups: ["create"] })
password: string;
@IsString({ groups: ["update"] })
newPassword: string;
}
Типичные проблемы:
Смешивание контекстов в одном поле
Чрезмерное дробление групп
Отсутствие базовой группы
validate
без параметровДублирование правил
В реальных приложениях группы часто определяются на основе контекста запроса:
function getValidationGroups(action: string): string[] {
switch (action) {
case "create":
return ["create"];
case "update":
return ["update"];
default:
return [];
}
}
И затем используются при валидации:
validate(dto, { groups: getValidationGroups(action) });
Кастомные валидаторы также поддерживают группы:
import { ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";
@ValidatorConstraint({ name: "customRule", async: false })
class CustomRule implements ValidatorConstraintInterface {
validate(value: any) {
return value === "allowed";
}
}
И подключение:
import { Validate } from "class-validator";
class SampleDto {
@Validate(CustomRule, { groups: ["special"] })
field: string;
}