Class-validator

Библиотека class-validator предназначена для декларативной валидации объектов в JavaScript и TypeScript с использованием декораторов. Основная идея заключается в том, чтобы описывать правила проверки прямо в классах моделей данных, а не разбрасывать проверки по бизнес-логике приложения.

Подход основан на метаданных и рефлексии: декораторы навешиваются на свойства классов, а затем библиотека анализирует эти метаданные и выполняет проверки.


Основные принципы работы

В основе библиотеки лежат три ключевых концепции:

1. Декларативность Правила описываются рядом с данными, а не в отдельных функциях.

2. Декораторы Используются аннотации вида @IsString(), @IsInt(), @Length().

3. Валидация объектов классов Проверяются не «сырые» объекты, а экземпляры классов.


Установка и базовая конфигурация

Для работы требуется включённая поддержка декораторов и рефлексии.

Установка:

npm install class-validator reflect-metadata

Дополнительно в TypeScript:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Инициализация рефлексии в проекте:

import "reflect-metadata";

Простейшая модель валидации

import { IsString, IsInt, Min, Max } from "class-validator";

class User {
  @IsString()
  name: string;

  @IsInt()
  @Min(18)
  @Max(120)
  age: number;
}

В этом примере:

  • name должен быть строкой
  • age должен быть целым числом от 18 до 120

Запуск валидации

import { validate } from "class-validator";

const user = new User();
user.name = "Alex";
user.age = 17;

validate(user).then(errors => {
  console.log(errors);
});

Если есть ошибки, возвращается массив объектов ValidationError.


Структура ValidationError

Каждая ошибка содержит:

  • property — имя поля
  • constraints — список нарушенных правил
  • children — вложенные ошибки (для объектов)

Пример:

{
  "property": "age",
  "constraints": {
    "min": "age must not be less than 18"
  }
}

Основные валидаторы

Проверка строк

@IsString()
@IsNotEmpty()
@Length(3, 20)

Проверка чисел

@IsNumber()
@IsInt()
@Min(0)
@Max(1000)

Проверка булевых значений

@IsBoolean()

Проверка массивов

@IsArray()
@ArrayMinSize(1)
@ArrayMaxSize(5)

Валидация вложенных объектов

Для сложных структур используется @ValidateNested():

import { ValidateNested } from "class-validator";
import { Type } from "class-transformer";

class Address {
  @IsString()
  city: string;
}

class User {
  @ValidateNested()
  @Type(() => Address)
  address: Address;
}

Ключевой момент: без @Type() вложенные объекты не будут корректно преобразованы.


Условная валидация через группы

Группы позволяют включать разные правила в зависимости от сценария:

class User {
  @IsEmail({}, { groups: ["create"] })
  email: string;

  @IsOptional()
  @IsString()
  password?: string;
}

Запуск:

validate(user, { groups: ["create"] });

Пользовательские валидаторы

Функциональный подход

import { ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";

@ValidatorConstraint({ name: "isEven", async: false })
class IsEvenConstraint implements ValidatorConstraintInterface {
  validate(value: number) {
    return value % 2 === 0;
  }

  defaultMessage() {
    return "Number must be even";
  }
}

Использование:

import { Validate } from "class-validator";

class Sample {
  @Validate(IsEvenConstraint)
  value: number;
}

Асинхронная валидация

Поддерживается работа с промисами, например при проверке уникальности в базе данных:

@ValidatorConstraint({ async: true })
class IsUniqueEmail {
  async validate(email: string) {
    const user = await database.findUser(email);
    return !user;
  }
}

Трансформация и приведение типов

Валидация часто используется вместе с преобразованием данных:

import { plainToInstance } from "class-transformer";

const obj = plainToInstance(User, rawData);
validate(obj);

Это позволяет автоматически превращать JSON в экземпляры классов.


Частые комбинации декораторов

Обязательное поле с ограничениями

@IsString()
@IsNotEmpty()
@Length(5, 50)

Необязательное поле

@IsOptional()
@IsString()

Число с диапазоном

@IsInt()
@Min(1)
@Max(10)

Работа с массивами объектов

class Item {
  @IsString()
  name: string;
}

class Order {
  @ValidateNested({ each: true })
  @Type(() => Item)
  items: Item[];
}

each: true заставляет валидировать каждый элемент массива отдельно.


Синхронная и асинхронная валидация

validateSync(object);

Используется когда нет асинхронных правил. Возвращает ошибки сразу, без Promise.


Интеграция с архитектурой приложений

Валидация часто размещается на уровне DTO (Data Transfer Objects), отделяя входные данные от бизнес-логики.

class CreateUserDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;
}

Такой подход снижает связность и упрощает поддержку кода.


Поведение при ошибках

По умолчанию библиотека возвращает полный список ошибок. Их можно:

  • фильтровать
  • преобразовывать в пользовательский формат
  • логировать

Пример упрощения:

errors.map(e => ({
  field: e.property,
  message: Object.values(e.constraints || {})
}));

Производительность и ограничения

При большом количестве объектов важно учитывать:

  • глубокая валидация увеличивает стоимость проверки
  • асинхронные валидаторы блокируют общий поток до завершения промисов
  • использование validateSync быстрее, но менее гибкое

Типичные ошибки при использовании

  • отсутствие emitDecoratorMetadata
  • забытый reflect-metadata
  • отсутствие @Type() для вложенных объектов
  • попытка валидировать plain object без преобразования в класс
  • смешивание синхронных и асинхронных валидаторов без учета await

Расширенные возможности

Библиотека позволяет:

  • комбинировать правила через кастомные декораторы
  • создавать переиспользуемые валидаторы
  • строить сложные схемы валидации без сторонних библиотек схем
  • интегрироваться с фреймворками, использующими DTO-архитектуру

Архитектурное значение валидации через декораторы

Использование декларативной валидации переносит ответственность за проверку данных ближе к их структуре. Это упрощает:

  • сопровождение кода
  • масштабирование моделей
  • повторное использование DTO
  • стандартизацию входных данных в приложении