В GraphQL-экосистеме валидация входных данных выполняет роль первого
уровня защиты бизнес-логики, предотвращая попадание некорректных
значений в резолверы и сервисный слой. В связке с
class-validator наиболее часто используется подход, при
котором входные схемы описываются через классы (DTO), а правила проверки
навешиваются декораторами непосредственно на поля.
GraphQL не навязывает строгую валидацию на уровне спецификации: типизация ограничивается примитивами (String, Int, Boolean и т.д.) и структурой схемы. Поэтому сложные ограничения (диапазоны, формат строк, условные проверки) реализуются на уровне приложения.
При использовании class-validator типичная архитектура
выглядит следующим образом:
Наиболее естественная интеграция достигается через библиотеку
type-graphql, где классы одновременно являются и
GraphQL-схемой, и объектами валидации.
import { InputType, Field, Int } from "type-graphql";
import { Length, IsEmail, Min, Max } from "class-validator";
@InputType()
export class CreateUserInput {
@Field()
@Length(2, 30)
name: string;
@Field()
@IsEmail()
email: string;
@Field(() => Int)
@Min(18)
@Max(120)
age: number;
}
В этом случае один и тот же класс выполняет двойную роль: формирует
GraphQL-схему и задаёт ограничения через декораторы
class-validator.
Сам по себе GraphQL не запускает class-validator. Для
этого требуется явный вызов валидации либо интеграционный слой.
В type-graphql валидация может включаться автоматически
через validate: true при построении схемы:
import { buildSchema } from "type-graphql";
const schema = await buildSchema({
resolvers: [UserResolver],
validate: true
});
При таком подходе входные объекты проходят через
class-validator до попадания в резолвер.
В более низкоуровневых интеграциях (например, Apollo Server без дополнительных обёрток) валидация выполняется вручную.
import { validate } from "class-validator";
import { plainToInstance } from "class-transformer";
async function createUser(_, args) {
const input = plainToInstance(CreateUserInput, args.input);
const errors = await validate(input);
if (errors.length > 0) {
throw new Error("Validation failed");
}
return userService.create(input);
}
Ключевой момент заключается в преобразовании plain-объекта в
экземпляр класса через class-transformer. Без этого
декораторы class-validator не будут корректно
обработаны.
GraphQL часто использует сложные входные структуры.
class-validator поддерживает вложенную проверку через
@ValidateNested и @Type.
import { InputType, Field } from "type-graphql";
import { ValidateNested, IsString } from "class-validator";
import { Type } from "class-transformer";
class ProfileInput {
@IsString()
bio: string;
}
@InputType()
class CreateUserInput {
@Field()
name: string;
@Field(() => ProfileInput)
@ValidateNested()
@Type(() => ProfileInput)
profile: ProfileInput;
}
Без @Type вложенная структура останется обычным
объектом, и валидация не будет применена корректно.
GraphQL часто передаёт списки сущностей, что требует специальной настройки валидации элементов массива.
import { IsArray, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
class RoleInput {
@IsString()
name: string;
}
@InputType()
class CreateUserInput {
@Field(() => [RoleInput])
@IsArray()
@ValidateNested({ each: true })
@Type(() => RoleInput)
roles: RoleInput[];
}
Флаг each: true обеспечивает проверку каждого элемента
массива отдельно.
В GraphQL-сценариях часто требуется логика, не укладывающаяся в стандартные декораторы. Для этого используются пользовательские валидаторы.
import { registerDecorator, ValidationOptions } from "class-validator";
function IsEven(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: "isEven",
target: object.constructor,
propertyName,
options: validationOptions,
validator: {
validate(value: number) {
return typeof value === "number" && value % 2 === 0;
}
}
});
};
}
Использование в GraphQL InputType:
@InputType()
class TestInput {
@Field()
@IsEven({ message: "Число должно быть чётным" })
value: number;
}
В NestJS GraphQL интеграция чаще всего реализуется через глобальные пайпы.
import { ValidationPipe } from "@nestjs/common";
app.useGlobalPipes(
new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: true
})
);
GraphQL resolver:
@Resolver()
export class UserResolver {
@Mutation(() => User)
createUser(@Args("input") input: CreateUserInput) {
return this.userService.create(input);
}
}
В этом случае ValidationPipe автоматически применяет
class-validator к входным DTO.
В GraphQL часто возникает проблема «лишних» полей, которые не описаны
в схеме, но приходят от клиента. class-validator решает это
через комбинацию с class-transformer.
import { Expose, Exclude } from "class-transformer";
@InputType()
class CreateUserInput {
@Field()
@Expose()
name: string;
@Exclude()
internalFlag: boolean;
}
При включённом whitelist лишние поля автоматически
удаляются, что снижает риск загрязнения бизнес-логики.
Некоторые проверки требуют обращения к базе данных: проверка уникальности email, существования сущности и т.д.
import { ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";
@ValidatorConstraint({ async: true })
class IsEmailAlreadyUsed implements ValidatorConstraintInterface {
async validate(email: string) {
const user = await userRepository.findOne({ email });
return !user;
}
}
Использование:
@Validate(IsEmailAlreadyUsed)
@IsEmail()
email: string;
Асинхронная валидация выполняется до входа в резолвер, что предотвращает лишние вызовы бизнес-логики.
Ошибки class-validator преобразуются в массив
ValidationError, который может быть трансформирован в
формат GraphQL-ошибок.
if (errors.length > 0) {
const formatted = errors.map(e => ({
field: e.property,
constraints: e.constraints
}));
throw new Error(JSON.stringify(formatted));
}
В более зрелых архитектурах ошибки преобразуются в
ApolloError с расширенной структурой
extensions.
GraphQL-схемы требуют явного описания всех полей, поэтому DTO
становятся центральным элементом архитектуры. При использовании
class-validator важно учитывать:
@Typeeach: true для полной проверкиТакой подход позволяет сохранять согласованность между схемой GraphQL и правилами валидации без дублирования логики в резолверах