В экосистеме Node.js сочетание ORM и декларативной валидации позволяет разделить ответственность между уровнем хранения данных и уровнем проверки бизнес-ограничений. Sequelize отвечает за работу с базой данных, маппинг моделей и выполнение SQL-запросов, тогда как Class-validator предоставляет механизм декларативного описания правил валидации через классы и декораторы.
Ключевая идея интеграции заключается в том, что Sequelize-модель не обязана содержать сложную валидационную логику. Вместо этого проверка данных выносится в отдельные сущности (DTO или доменные модели), которые проходят валидацию до или во время сохранения в базу.
Sequelize предоставляет собственную систему валидации на уровне полей модели, однако она ограничена по выразительности и плохо масштабируется при росте доменной логики. Class-validator решает эту проблему за счёт:
Sequelize при этом остаётся исключительно слоем доступа к данным:
Наиболее распространённый подход — введение DTO (Data Transfer Object), которые описывают структуру входных данных и валидируются до взаимодействия с Sequelize.
import { IsEmail, IsString, Length, IsInt, Min } fr om 'class-validator';
export class CreateUserDto {
@IsString()
@Length(3, 50)
username;
@IsEmail()
email;
@IsInt()
@Min(18)
age;
}
DTO не зависит от ORM и может использоваться в контроллерах, сервисах или очередях сообщений.
Типичный поток данных включает:
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
import { CreateUserDto } from './dto/CreateUserDto';
async function validateCreateUser(input) {
const dto = plainToInstance(CreateUserDto, input);
const errors = await validate(dto);
if (errors.length > 0) {
throw new Error(JSON.stringify(errors));
}
return dto;
}
После успешной проверки данные передаются в Sequelize:
const dto = await validateCreateUser(req.body);
const user = await User.create({
username: dto.username,
email: dto.email,
age: dto.age
});
Sequelize предоставляет механизм хуков (beforeValidate,
beforeCreate, beforeUpdate), который может
использоваться для интеграции Class-validator на уровне модели.
User.beforeValidate(async (user, options) => {
const dto = plainToInstance(CreateUserDto, user.toJSON());
const errors = await validate(dto);
if (errors.length > 0) {
throw new Error('Validation failed');
}
});
Данный подход переносит ответственность за проверку внутрь модели, однако увеличивает связанность ORM и валидационного слоя.
Sequelize позволяет определять кастомные валидаторы прямо в описании модели. Class-validator можно использовать как внешнюю систему проверки.
import { Model, DataTypes } from 'sequelize';
export class User extends Model {}
User.init({
username: {
type: DataTypes.STRING,
allowNull: false,
validate: {
async isValid(value) {
const dto = plainToInstance(CreateUserDto, { username: value });
const errors = await validate(dto, { skipMissingProperties: true });
if (errors.length > 0) {
throw new Error('Invalid username');
}
}
}
}
}, {
sequelize,
modelName: 'User'
});
Class-validator поддерживает создание собственных правил через
ValidatorConstraint.
import {
ValidatorConstraint,
ValidatorConstraintInterface
} from 'class-validator';
@ValidatorConstraint({ async: true })
export class IsUniqueEmailConstraint {
async validate(email) {
const user = await User.findOne({ wh ere: { email } });
return !user;
}
}
Использование:
import { Validate } from 'class-validator';
export class CreateUserDto {
@IsEmail()
@Validate(IsUniqueEmailConstraint)
email;
}
Здесь происходит пересечение слоёв: DTO получает доступ к модели Sequelize, что требует осторожности в архитектурном проектировании, чтобы избежать циклических зависимостей.
Class-validator поддерживает асинхронные проверки, что важно при работе с базой данных через Sequelize.
Проблемные аспекты:
Оптимизация достигается через:
При использовании транзакций важно учитывать момент выполнения валидации.
await sequelize.transaction(async (t) => {
const dto = await validateCreateUser(input);
await User.create({
username: dto.username,
email: dto.email
}, { transaction: t });
});
Валидация выполняется до записи в базу, что исключает необходимость отката уже невалидных данных.
При использовании Class-validator и Sequelize одновременно возникает необходимость синхронизации:
Расхождение между ними приводит к:
Часто используется стратегия:
Наиболее устойчивой архитектурой считается вариант, при котором:
class UserService {
async createUser(input) {
const dto = await validateCreateUser(input);
return User.create({
username: dto.username,
email: dto.email,
age: dto.age
});
}
}
Такой подход минимизирует связанность и упрощает тестирование.
Class-validator возвращает структурированный массив ошибок, содержащий:
Типичный формат обработки:
const errors = await validate(dto);
const formatted = errors.map(e => ({
field: e.property,
constraints: e.constraints
}));
При интеграции с Sequelize важно разделять:
Sequelize поддерживает сложные связи, Class-validator — вложенные
объекты через ValidateNested.
import { ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
class ProfileDto {
@IsString()
bio;
}
class CreateUserDto {
@IsString()
username;
@ValidateNested()
@Type(() => ProfileDto)
profile;
}
Данные из DTO затем могут маппиться в связанные модели Sequelize:
User.create({
username: dto.username,
Profile: dto.profile
}, {
include: [Profile]
});
Комбинация Class-validator и Sequelize формирует двухуровневую систему защиты данных:
Такая модель позволяет:
При совместном использовании возникают характерные сложности:
Эти проблемы обычно решаются архитектурным разделением ответственности и строгим ограничением доступа DTO к ORM.