В GraphQL-схемах основная ответственность за корректность данных традиционно распределяется между типами схемы и резолверами. Однако на практике одного только механизма типизации GraphQL недостаточно: сложные бизнес-правила, составные структуры и условные ограничения требуют дополнительного слоя валидации. В таких сценариях используется библиотека структурной валидации Superstruct, позволяющая задавать строгие правила описания данных и проверять их на этапе выполнения.
Superstruct работает как декларативный валидатор: структуры описываются как композиция примитивов и правил, после чего используются для проверки входных значений. При интеграции с GraphQL это позволяет централизовать проверку аргументов резолверов и входных DTO без дублирования логики в каждом обработчике.
GraphQL резолверы получают входные данные в виде аргументов. Именно этот слой становится точкой интеграции с Superstruct:
import { object, string, number, size, validate } fr om "superstruct";
const CreateUserInput = object({
name: size(string(), 2, 50),
email: string(),
age: number()
});
function createUser(_, args) {
const [error, value] = validate(args.input, CreateUserInput);
if (error) {
throw new Error(error.message);
}
return userService.create(value);
}
В этом примере структура CreateUserInput описывает
контракт входных данных. GraphQL-схема при этом остаётся источником
типов на уровне API, а Superstruct добавляет строгую проверку
бизнес-ограничений.
GraphQL выполняет роль контракта передачи данных, но не гарантирует соблюдение сложных ограничений. Например:
Superstruct закрывает этот слой, сохраняя GraphQL как транспортную и типовую абстракцию.
Типичная схема разделения:
В больших проектах повторение валидации в каждом резолвере приводит к дублированию логики. Решение — обобщённый wrapper:
import { validate } from "superstruct";
function withValidation(structure, resolver) {
return (parent, args, context, info) => {
const [error, value] = validate(args, structure);
if (error) {
throw new Error(error.message);
}
return resolver(parent, value, context, info);
};
}
Использование:
const resolvers = {
Mutation: {
createUser: withValidation(CreateUserInput, (_, input) => {
return userService.create(input);
})
}
};
Такой подход устраняет необходимость ручной проверки аргументов в каждом резолвере и делает слой GraphQL тонким.
GraphQL часто использует вложенные структуры input-типов. Superstruct поддерживает композицию структур, что позволяет зеркально отражать сложность схемы:
import { object, string, number, array } from "superstruct";
const Address = object({
city: string(),
street: string()
});
const UserProfile = object({
name: string(),
addresses: array(Address),
age: number()
});
При передаче вложенных объектов из GraphQL структура сохраняется без дополнительной обработки.
В GraphQL различаются типы операций, и для них часто применяются разные правила:
Superstruct позволяет разделять схемы:
const UserFilter = object({
search: string(),
lim it: number()
});
const CreateUser = object({
name: string(),
email: string()
});
И использовать их независимо в резолверах соответствующих операций.
GraphQL ожидает структурированные ошибки. Superstruct возвращает объект ошибки, который можно трансформировать:
function formatValidationError(error) {
return {
message: "Validation failed",
details: error.failures ? error.failures() : []
};
}
Использование в резолвере:
const [error, value] = validate(input, CreateUser);
if (error) {
throw new Error(JSON.stringify(formatValidationError(error)));
}
В более зрелых системах ошибки преобразуются в GraphQL-compliant формат через кастомные error классы.
Superstruct поддерживает кастомные проверки через
refine, что особенно важно при интеграции с GraphQL:
import { string, refine } fr om "superstruct";
const EvenStringLength = refine(string(), "EvenStringLength", (value) => {
return value.length % 2 === 0;
});
Такой подход позволяет реализовать бизнес-ограничения, которые невозможно выразить через GraphQL scalar-типы.
В сложных API часто используются динамические фильтры. Superstruct позволяет строить структуры программно:
function createFilterStruct(fields) {
const shape = {};
for (const field of fields) {
shape[field] = string();
}
return object(shape);
}
Это особенно полезно при построении универсальных GraphQL фильтров и административных API.
В контексте Apollo Server интеграция Superstruct реализуется через слой резолверов:
import { ApolloServer } from "@apollo/server";
const server = new ApolloServer({
typeDefs,
resolvers
});
Superstruct не вмешивается в lifecycle GraphQL сервера, а работает исключительно на уровне бизнес-валидации входных данных, что делает его совместимым с любым сервером GraphQL.
При использовании TypeScript Superstruct может выступать как runtime-валидация поверх compile-time типов:
import { Infer } from "superstruct";
type CreateUser = Infer<typeof CreateUserInput>;
Это создаёт единый источник истины: структура одновременно описывает runtime-валидацию и типы приложения.
При высокой нагрузке важно минимизировать стоимость проверки. Superstruct позволяет:
Пример оптимизации:
const cachedStruct = CreateUserInput;
function resolver(_, args) {
const [error, value] = validate(args.input, cachedStruct);
if (error) throw error;
return service.create(value);
}
Superstruct поддерживает композицию, что особенно полезно для масштабируемых GraphQL API:
const Timestamped = object({
createdAt: string(),
updatedAt: string()
});
const User = object({
name: string(),
email: string(),
meta: Timestamped
});
Это позволяет повторно использовать бизнес-структуры между различными резолверами.
GraphQL допускает nullable и optional поля, но Superstruct требует явного описания:
import { object, string, optional, defaulted } from "superstruct";
const QueryInput = object({
search: optional(string()),
lim it: defaulted(number(), 10)
});
Это устраняет неоднозначность поведения аргументов в резолверах.
При масштабировании GraphQL API структура валидации часто фрагментируется. Superstruct позволяет вынести её в отдельный слой доменной модели:
/domain
/user
struct.js
service.js
/product
struct.js
GraphQL слой в этом случае становится тонкой оболочкой над доменной логикой, а Superstruct обеспечивает единые правила данных на уровне всего API.