Валидация входящих данных в веб-приложениях на Node.js — один из ключевых слоёв защиты и стабильности. В экосистеме Express это почти всегда реализуется через middleware, позволяя отделить бизнес-логику от проверки корректности входных данных. Библиотека Ajv (Another JSON Schema Validator) становится центральным инструментом, когда требуется строгая и высокопроизводительная проверка JSON-структур по схемам.
Express не навязывает архитектуру, поэтому middleware-валидация формирует стандартизированный слой обработки запросов:
Такой подход позволяет централизовать контроль качества входных данных.
В основе интеграции лежит создание экземпляра валидатора и его повторное использование для всех схем.
import express fr om "express";
import Ajv from "ajv";
const app = express();
app.use(express.json());
const ajv = new Ajv({
allErrors: true,
removeAdditional: true,
strict: false
});
Ключевые параметры конфигурации:
allErrors: true — собирает все ошибки, а не
останавливается на первой;removeAdditional: true — удаляет лишние поля из
входящего объекта;strict: false — снижает жёсткость режима проверки
схем.Основной паттерн использования Ajv в Express — фабрика middleware, принимающая JSON Schema.
const validate = (schema) => {
const validator = ajv.compile(schema);
return (req, res, next) => {
const valid = validator(req.body);
if (valid) {
return next();
}
return res.status(400).json({
message: "Validation error",
errors: validator.errors
});
};
};
Здесь происходит ключевое:
Наиболее распространённый сценарий — проверка
req.body.
const userSchema = {
type: "object",
properties: {
email: { type: "string", format: "email" },
password: { type: "string", minLength: 6 }
},
required: ["email", "password"],
additionalProperties: false
};
app.post("/register", validate(userSchema), (req, res) => {
res.json({ status: "ok" });
});
Особенности:
required определяет обязательные поля;additionalProperties: false предотвращает “мусорные”
данные;format: "email" использует встроенные форматы Ajv.Для req.query часто требуется отдельная схема, так как
типы приходят в виде строк.
const querySchema = {
type: "object",
properties: {
page: { type: "string", pattern: "^[0-9]+$" },
lim it: { type: "string", pattern: "^[0-9]+$" }
},
required: [],
additionalProperties: false
};
app.get("/users", validateQuery(querySchema), (req, res) => {
res.json([]);
});
Middleware для query:
const validateQuery = (schema) => {
const validator = ajv.compile(schema);
return (req, res, next) => {
const valid = validator(req.query);
if (!valid) {
return res.status(400).json({ errors: validator.errors });
}
next();
};
};
Параметры req.params часто используются для
идентификаторов ресурсов.
const paramsSchema = {
type: "object",
properties: {
id: { type: "string", pattern: "^[0-9]+$" }
},
required: ["id"],
additionalProperties: false
};
app.get("/users/:id", validateParams(paramsSchema), (req, res) => {
res.json({ id: req.params.id });
});
Практика показывает, что лучше использовать универсальный middleware с параметром источника данных.
const validateRequest = (schema, source = "body") => {
const validator = ajv.compile(schema);
return (req, res, next) => {
const data = req[source];
const valid = validator(data);
if (!valid) {
return res.status(400).json({
source,
errors: validator.errors
});
}
next();
};
};
Использование:
app.post("/login", validateRequest(loginSchema, "body"), handler);
app.get("/search", validateRequest(searchSchema, "query"), handler);
app.get("/item/:id", validateRequest(idSchema, "params"), handler);
В крупных приложениях важно не дублировать формат ошибок. Вместо прямого возврата из middleware можно пробрасывать ошибки в централизованный обработчик.
class ValidationError extends Error {
constructor(errors) {
super("Validation error");
this.errors = errors;
}
}
Middleware:
if (!valid) {
return next(new ValidationError(validator.errors));
}
Глобальный обработчик:
app.use((err, req, res, next) => {
if (err instanceof ValidationError) {
return res.status(400).json({
message: err.message,
details: err.errors
});
}
res.status(500).json({ message: "Internal error" });
});
Ajv работает быстро, но компиляция схем — дорогая операция. Поэтому критически важно:
Оптимальный подход:
const validators = {
user: ajv.compile(userSchema),
login: ajv.compile(loginSchema)
};
Иногда схема зависит от входных параметров (например, роли пользователя).
const createSchemaByRole = (role) => {
if (role === "admin") {
return {
type: "object",
properties: {
accessLevel: { type: "number" }
},
required: ["accessLevel"]
};
}
return {
type: "object",
properties: {
username: { type: "string" }
},
required: ["username"]
};
};
Middleware:
const dynamicValidate = (roleExtractor) => {
return (req, res, next) => {
const schema = createSchemaByRole(roleExtractor(req));
const validate = ajv.compile(schema);
if (!validate(req.body)) {
return res.status(400).json(validate.errors);
}
next();
};
};
Ajv поддерживает расширение логики через кастомные keyword.
ajv.addKeyword({
keyword: "isOdd",
validate: (schema, data) => {
return typeof data === "number" && data % 2 === 1;
}
});
Схема:
const schema = {
type: "object",
properties: {
value: { type: "number", isOdd: true }
}
};
Express позволяет комбинировать middleware, создавая слоистую архитектуру:
app.post(
"/orders",
authMiddleware,
validateOrderSchema,
businessLogicMiddleware,
controller
);
Преимущества:
additionalProperties;params и query, фокус только
на body.В крупных проектах удобно выделять слой схем:
/schemas
user.schema.js
auth.schema.js
/middlewares
validate.js
validateQuery.js
validateParams.js
Это позволяет:
Использование Ajv в middleware-слое напрямую влияет на безопасность:
Строгая схема становится контрактом между клиентом и сервером, а Express middleware — механизмом принудительного соблюдения этого контракта.