TypeStack/class-validator для TypeScript: полное руководство с примерами
1. Что это и зачем нужна
Библиотека TypeStack/class-validator — это мощный инструмент для валидации данных в TypeScript и JavaScript, основанный на декораторах. Она позволяет разработчикам описывать правила валидации непосредственно в классах, используя декораторы свойств. Это делает код более читаемым, самодокументируемым и значительно упрощает обработку входящих данных, особенно при работе с REST API, формами или любыми другими источниками данных.
Зачем она нужна? В современной веб-разработке данные приходят из множества источников: HTTP-запросы, файлы конфигурации, базы данных. Без надёжной валидации эти данные могут привести к ошибкам времени выполнения, уязвимостям безопасности или просто к некорректной работе приложения. class-validator решает эту проблему, предоставляя декларативный способ проверки данных. Вместо того чтобы писать громоздкие if-else конструкции, вы просто добавляете декораторы к свойствам класса, и библиотека автоматически проверяет их соответствие заданным правилам.
Ещё одно ключевое преимущество — тесная интеграция с TypeScript. Декораторы позволяют не только валидировать данные, но и определять их типы, что делает процесс разработки более безопасным и предсказуемым. Библиотека поддерживает широкий спектр встроенных валидаторов (от простых проверок на длину строки до сложных регулярных выражений) и позволяет легко создавать собственные кастомные валидаторы.
2. Установка
Для установки библиотеки class-validator и её обязательной зависимости reflect-metadata выполните следующую команду в терминале вашего проекта:
npm install class-validator reflect-metadata
После установки необходимо убедиться, что в вашем файле tsconfig.json включены следующие опции для корректной работы декораторов:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Также важно импортировать reflect-metadata в самом начале вашего приложения (обычно в главном файле index.ts или app.ts):
import "reflect-metadata";
3. Быстрый старт
Рассмотрим минимальный рабочий пример. Создадим класс пользователя с валидацией полей:
import "reflect-metadata";
import { validate, IsString, IsEmail, MinLength } from "class-validator";
class User {
@IsString()
@MinLength(3, { message: "Имя должно содержать минимум 3 символа" })
name: string;
@IsEmail({}, { message: "Некорректный email" })
email: string;
constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
}
async function main() {
const user = new User("Jo", "invalid-email");
const errors = await validate(user);
if (errors.length > 0) {
console.log("Ошибки валидации:");
errors.forEach(error => {
console.log(`- ${error.property}: ${Object.values(error.constraints).join(", ")}`);
});
} else {
console.log("Валидация пройдена успешно!");
}
}
main();
// Вывод:
// Ошибки валидации:
// - name: Имя должно содержать минимум 3 символа
// - email: Некорректный email
В этом примере мы создали класс User, добавили декораторы к его свойствам и вызвали функцию validate(). Она возвращает массив ошибок, если данные не соответствуют правилам.
4. Основные методы/функции/классы
Рассмотрим 8 ключевых функций и классов библиотеки class-validator.
4.1. validate(object: object, options?: ValidatorOptions): Promise<ValidationError[]>
Что делает: Основная функция для валидации объекта. Принимает экземпляр класса и возвращает массив ошибок ValidationError. Если ошибок нет, возвращается пустой массив.
import { validate, IsString } from "class-validator";
class Person {
@IsString()
name: string;
constructor(name: string) {
this.name = name;
}
}
const person = new Person(123 as any);
validate(person).then(errors => {
console.log(errors.length); // 1
});
4.2. validateOrReject(object: object, options?: ValidatorOptions): Promise<void>
Что делает: Аналог validate(), но в случае ошибок выбрасывает исключение (reject) с массивом ошибок. Удобно использовать с async/await и try/catch.
import { validateOrReject, IsNumber } from "class-validator";
class Product {
@IsNumber()
price: number;
constructor(price: number) {
this.price = price;
}
}
async function checkProduct() {
const product = new Product("not a number" as any);
try {
await validateOrReject(product);
console.log("Валидация пройдена");
} catch (errors) {
console.log("Ошибка:", errors[0].constraints);
}
}
checkProduct();
4.3. isDefined(value: any): boolean
Что делает: Проверяет, определено ли значение (не null и не undefined). Часто используется в кастомных валидаторах.
import { isDefined } from "class-validator";
console.log(isDefined("hello")); // true
console.log(isDefined(null)); // false
console.log(isDefined(undefined)); // false
4.4. isEmpty(value: any): boolean
Что делает: Проверяет, является ли значение "пустым". Для строк — пустая строка, для массивов — длина 0, для объектов — отсутствие ключей.
import { isEmpty } from "class-validator";
console.log(isEmpty("")); // true
console.log(isEmpty([])); // true
console.log(isEmpty({})); // true
console.log(isEmpty("text")); // false
4.5. isEmail(value: string): boolean
Что делает: Проверяет, является ли строка корректным email-адресом. Использует встроенное регулярное выражение.
import { isEmail } from "class-validator";
console.log(isEmail("user@example.com")); // true
console.log(isEmail("invalid-email")); // false
4.6. isInt(value: any): boolean
Что делает: Проверяет, является ли значение целым числом. Не преобразует типы, то есть строка "123" вернёт false.
import { isInt } from "class-validator";
console.log(isInt(42)); // true
console.log(isInt(3.14)); // false
console.log(isInt("42")); // false
4.7. isString(value: any): boolean
Что делает: Проверяет, является ли значение строкой. Аналог typeof value === 'string'.
import { isString } from "class-validator";
console.log(isString("hello")); // true
console.log(isString(123)); // false
console.log(isString(null)); // false
4.8. ValidationError (класс)
Что делает: Класс, представляющий одну ошибку валидации. Содержит свойства property (имя поля), constraints (объект с сообщениями об ошибках) и children (для вложенных объектов).
import { validate, IsString } from "class-validator";
class Test {
@IsString()
field: any;
}
const test = new Test();
test.field = 123;
validate(test).then(errors => {
if (errors.length > 0) {
const error = errors[0];
console.log(error.property); // "field"
console.log(error.constraints); // { isString: "field must be a string" }
}
});
5. Реальный практический пример
Создадим полноценный пример валидации данных для регистрации пользователя в веб-приложении. Пример включает вложенные объекты, кастомные сообщения и асинхронную проверку.
import "reflect-metadata";
import {
validate,
IsString,
IsEmail,
MinLength,
MaxLength,
IsOptional,
IsArray,
ValidateNested,
IsEnum,
Matches,
ArrayMinSize,
IsBoolean,
Min,
Max,
IsInt
} from "class-validator";
import { Type } from "class-transformer";
// Перечисление для ролей пользователя
enum UserRole {
ADMIN = "admin",
USER = "user",
MODERATOR = "moderator"
}
// Класс для адреса (вложенный объект)
class Address {
@IsString({ message: "Страна должна быть строкой" })
@MinLength(2, { message: "Страна должна содержать минимум 2 символа" })
country: string;
@IsString()
@MinLength(2)
city: string;
@IsString()
@Matches(/^\d{6}$/, { message: "Почтовый индекс должен состоять из 6 цифр" })
zipCode: string;
constructor(country: string, city: string, zipCode: string) {
this.country = country;
this.city = city;
this.zipCode = zipCode;
}
}
// Основной класс пользователя
class CreateUserDto {
@IsString({ message: "Имя пользователя обязательно" })
@MinLength(3, { message: "Имя пользователя должно быть минимум 3 символа" })
@MaxLength(20, { message: "Имя пользователя должно быть максимум 20 символов" })
username: string;
@IsEmail({}, { message: "Пожалуйста, укажите корректный email" })
email: string;
@IsString()
@MinLength(8, { message: "Пароль должен содержать минимум 8 символов" })
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, {
message: "Пароль должен содержать заглавные, строчные буквы и цифры"
})
password: string;
@IsInt({ message: "Возраст должен быть целым числом" })
@Min(18, { message: "Вам должно быть минимум 18 лет" })
@Max(120, { message: "Некорректный возраст" })
age: number;
@IsOptional()
@IsString()
phone?: string;
@IsEnum(UserRole, { message: "Роль должна быть admin, user или moderator" })
role: UserRole;
@IsBoolean({ message: "Согласие с условиями обязательно" })
agreeToTerms: boolean;
@ValidateNested()
@Type(() => Address)
address: Address;
@IsOptional()
@IsArray()
@IsString({ each: true, message: "Каждый тег должен быть строкой" })
@ArrayMinSize(1, { message: "Добавьте хотя бы один тег" })
tags?: string[];
constructor(
username: string,
email: string,
password: string,
age: number,
role: UserRole,
agreeToTerms: boolean,
address: Address,
tags?: string[]
) {
this.username = username;
this.email = email;
this.password = password;
this.age = age;
this.role = role;
this.agreeToTerms = agreeToTerms;
this.address = address;
this.tags = tags;
}
}
// Функция для обработки регистрации
async function registerUser(userData: CreateUserDto) {
const errors = await validate(userData, {
validationError: { target: false, value: false },
whitelist: true,
forbidNonWhitelisted: true
});
if (errors.length > 0) {
console.error("Ошибки валидации:");
const formattedErrors = errors.map(error => ({
field: error.property,
messages: Object.values(error.constraints || {})
}));
console.error(JSON.stringify(formattedErrors, null, 2));
return { success: false, errors: formattedErrors };
}
console.log("Пользователь успешно зарегистрирован:", userData.username);
return { success: true, user: userData };
}
// Пример использования
async function main() {
const address = new Address("Россия", "Москва", "123456");
const user = new CreateUserDto(
"john_doe",
"john@example.com",
"StrongPass1",
25,
UserRole.USER,
true,
address,
["javascript", "typescript"]
);
const result = await registerUser(user);
console.log(result);
// Пример с ошибками
const badUser = new CreateUserDto(
"jo",
"invalid-email",
"weak",
15,
"unknown" as UserRole,
false,
new Address("", "", "123"),
[]
);
const badResult = await registerUser(badUser);
console.log(badResult);
}
main();
Этот пример демонстрирует: вложенную валидацию (@ValidateNested), кастомные сообщения об ошибках, проверку перечислений (@IsEnum), регулярные выражения (@Matches), опциональные поля (@IsOptional) и массивы с валидацией элементов (each: true).