Zod протык Express-validator: два падходы да верыфікацыі ў Express
Поручаецца праблемам падтверджэння запыткаў на адпаведнасці схеме з Zod у працэ паўтаральных мідлвэраў express-validator, адносна ланцоўкавага падходу, і ахватва настройку, форматаванне абэктаў паказчых адзінакоў і частыя працоўныя падступкі.
Работа з недазверенымі дадзеннямі ўваходу — адна з першых проблем, якія павінны быць рашаныя кожной API на базе Express, і існуе больш за адна способа яе рашэння — ад бібліятак, якія спачатку ствараюць схему, да болей процедурных верыфікатораў, базаваных на ланцохах. У гэтым артыкуле рассмотрваюцца оба падходы, пачынаючы з методу, які базуецца на схеме і створаны з адпамогай Zod.
Верыфікацыя запитоў з адпамогай Zod
Express сама по сабе не выкананяе жадной верыфікацыі прыходзячых дадзенняў. Без пераканалення на рубежы, обробнікі маршрутаў прыманяюць необработаныя значэння req.body, req.query і req.params у тым стане, у якім яны прыбылі — числовыя полья, якія на самай працэ ўсё-такі є строкамі, полья, якія зовсім не ўжо існуюць, а таксама пакеты дадзенняў, форма якіх стварае проблемы толькі тады, калі яны доходзяць да вашай бізнес-логікі.
Zod рашае гэтыя проблемы, дазваляючы вам апісваць патрабуемыя форматы дадзейнаў як схемы на базе TypeScript. Вы задаёте схему аднойчы, вырашваеце статычны тип з яе за дапамою z.infer, і парсуеце прыходзячыя дадзеныя на роўні HTTP-шара, таким чынам усе наступныя элементы системы бачаць толькі правамерныя дадзеныя. Усё, што не праходзіць перакананне, можа стаць адпаведным адпаведнам HTTP-званам 400 пры тым, як ўжо запускаецца код обробніка.
У прыкладах нижэй викорыстоўваецца Zod 4 (z.email(), z.uuid(), z.coerce), а таксама мідлвэр для пераканання ў Express, дапаможнік для форматавання адзяўок і спіс распасцялаў.
Прыямыя патрабаванні
Вам знадобіцца версія Node.js 26, Zod 4 (npm i zod) і Express з його типовыміявленнямі (npm i express і npm i -D @types/express). Старыя синтаксісы Zod 3 у формате ланцюга, такія як z.string().email(), все ўсё працуюць у версіі v4, але ўжо не падтрымваюцца — кращэ викорыстоўваць новейшыя функціі верхньага рангу, паказаныя нижэй.
Акларацыя схем
// schemas.ts
import { z } from 'zod';
export const createUserSchema = z.object({
email: z.email(),
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(150).optional()
});
export type CreateUserInput = z.infer<typeof createUserSchema>;
export const userIdParamSchema = z.object({
id: z.uuid()
});
export const listUsersQuerySchema = z.object({
limit: z.coerce.number().int().min(1).max(100).default(10),
q: z.string().trim().min(1).optional()
});
z.coerce.number() ўжытковы для значэнняў у запитовых строках, адтакуе ўсё, што чытаецца з HTTP-запиту, прыходзіць у вигляде строкі незалежна ад яго логічнага типу. Кращэ викорыстоўваць safeParse замест parse на грані обробкі, каб вы моглі контролюваць результуючы статус HTTP і тэла адпаведзі.
Адносна стабільная форматаваннея паказаў пра бяды
Пераканаліцуйте ZodError.issues у адзін стабільны JSON-структуру замест таго, калі падэння форматавання адзначаюцца окалічна ў кожнай маршруце. Zod 4 таксама предстаўляе z.flattenError() для плоскага карточнага мапы падзеяў, а z.treeifyError() — для вёсканай структуры, якая адпаведае схеме.
// format-zod-error.ts
import { ZodError } from 'zod';
export function formatZodError(error: ZodError) {
return {
message: 'Validation failed',
issues: error.issues.map((issue) => ({
path: issue.path.join('.') || '(root)',
message: issue.message,
code: issue.code
}))
};
}
Мідлвэр для верыфікацыі
Пераканаліцуйте body, query і params прычымо да запуску обробніка маршрута, а пасля зберыце распакаваныя значэнні зноў, каб обробнік отрымаў дадзеныя з правым типам і пасля пераканаліцацыі.
// validate.ts
import { NextFunction, Request, Response } from 'express';
import { ZodType } from 'zod';
import { formatZodError } from './format-zod-error';
type RequestSchemas = {
body?: ZodType;
query?: ZodType;
params?: ZodType;
};
export function validate(schemas: RequestSchemas) {
return (req: Request, res: Response, next: NextFunction) => {
const parseOrReject = (schema: ZodType, value: unknown) => {
const parsed = schema.safeParse(value);
if (!parsed.success) {
res.status(400).json(formatZodError(parsed.error));
return null;
}
return parsed.data;
};
if (schemas.body) {
const body = parseOrReject(schemas.body, req.body);
if (body === null) return;
req.body = body;
}
if (schemas.query) {
const query = parseOrReject(schemas.query, req.query);
if (query === null) return;
res.locals.query = query;
}
if (schemas.params) {
const params = parseOrReject(schemas.params, req.params);
if (params === null) return;
res.locals.params = params;
}
next();
};
}
Падключыце яго да кожнай маршруцы так:
app.post('/users', validate({ body: createUserSchema }), (req, res) => {
// req.body is CreateUserInput
res.status(201).json({ id: crypto.randomUUID(), ...req.body });
});
app.get('/users', validate({ query: listUsersQuerySchema }), (req, res) => {
const { limit, q } = res.locals.query;
// ...
});
app.get('/users/:id', validate({ params: userIdParamSchema }), (req, res) => {
const { id } = res.locals.params;
// ...
});
Рэзультаты запитаў і параметраў зберагаюцца ў res.locals, таму што типы Express адносна req.query/req.params спрацоўваюць іх як звычныя карты страк; прымусовая замена іх безпосередна створыць суперсчэпленне з гэтымі типамі.
Адзінакі
- Строкі з запиту завжды ёсць строкамі — для числаў і логічных значэнняў выкорыстоўваюце
z.coerce(альбоz.string()плюс трансфарматырацыю). parseвыклікае первасныZodError; або перехапіце яго і самі ператворыце на код 400, або заместа таго выкорыстоўваюцеsafeParse.- Шэмы об’ектаў Zod за значчынкай адсілаюць неканацэнныя ключы; ўвесь час дадавайте
.strict(), каб іх адхіліць. - Тыпы, якія выважваюцца аўтаматычна, такія як
CreateUserInput, існуюць толькі пад час компілявання — завжды таксама выканайце парсінг на межы. - У Zod 4
z.uuid()пераканальваеся па новейшай, строгей спэцыфікацыі UUID; якщо вам проста патрэбны генерычныя шаблоны з восьмі, чатырох, чатырох і дванаццаці шістнадцаткавымі цифрамі без строгіяў правілаў, тады выкорыстоўваюцеz.guid().
Альтэрнатыва: Атрыбутаванне на адной з сэрвісных складоў з express-validator
Zod – гэта не ўсё спосабы запобегчы некальканосным дадзенням ад працэйобрабатчаў. Аплікацыі на Express даўно выкорыстоўваюць express-validator – бібліятэку, створаную спецыяльна як мідлвэр для Express, і яна выкарыстоўвае іншы падход да таго ж задачы.
Уявіце запит на рэўістрацыю падобны да гэтага:
{
"email": "hello",
"password": "123"
}
Якщо кантролер пераглядае гэты пакет дадзення безпосередна, кожнаму полю трэба аддзельная ручная перапачатка, што быстра ператвараецца на масу умов, якія спалучаюць перапачатку з бізнес-логікай:
if (!email) ...
if (!email.includes("@")) ...
if (!password) ...
if (password.length < 8) ...
express-validator пераносіць гэтую логіку з кантролера ў спецыяльны крок мідлвэра, так што запит праходзіць через перапачатку раней, чым досягне вашага працэйобрабатча:
Request
↓
Validation
↓
Controller
↓
Business Logic
Гэтае адделенне – галоўная мета бібліятэкі: кантролер застаецца вялікі, каб выканаў толькі тое, для чаго ён прызначаны.
Ёсць калі запускайце, установіце пакет:
npm install express-validator
З'явіце памятку body і створыце ланцюг пераканальнення для кожнага поля, якое вас цікавіце:
import { body } from "express-validator";
export const registerValidator = [
body("email")
.isEmail()
.withMessage("Invalid email"), body("password")
.isLength({ min: 8 })
.withMessage("Password must contain at least 8 characters"), body("username")
.notEmpty()
.withMessage("Username is required"),
];
Прыўяжыце гэты мідлвард до маршруту, раней за контролер:
router.post(
"/register",
registerValidator,
registerController
);
Лячэнне пераканальнення сама по сабе недастатнія — вам яшчэ трэба прачытаць усія бяглы, якія былі збораны пад час пераканальнення:
import { validationResult } from "express-validator";
const errors = validationResult(req);if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array(),
});
}
За наявнасцю такога пераканальнення некоректныя даны адхоўваюцца з кодам 400 прычымо да запуску будь-якай бізнес-логіки.
Вбудованыя пераканальнікі добра падхоўляюцься да распашчытых ситуацый:
.isEmail()
.isLength()
.notEmpty()
.isInt()
Але рэальныя прыкладніцы часта патрабуюць правіл, якія бібліятэка не можа знайсці заздалегідь — напрыклад, пад час рэгістрацыі можа знадобіцца пераканаліць, чы не ўжо зайняты той або е-паштовы адрес. Для гэтага і існуе .custom():
body("email")
.isEmail()
.bail()
.custom(async (email) => {
const user = await User.findOne({ email });
if (user) {
throw new Error("Email already registered");
} return true;
});
Спецыяльныя верыфікаторы можуць быть асінхроннымі, што робіць іх падходямымі для запытанняў да базы дадзеных і іншых пераконтрацоў, якія залежаць ад вашай сэрвіснай логікі. Зверніце увагу на вызов .bail() прычынам спецыяльнай пераконтроўкі — ён праходзіць праз рэшту ланцюга, уклjuчаючы асінхронны запыт, якщо адреса электранай павядомлення вяліка ўжо пераконтрацыю .isEmail(), тэму чаго ухілваецца ад непатрэбнай поўтарной пераадрасавання да базы дадзеных.
Выбір между гэтымі двумя бібліятекамі — або Joi, іншым усталеным варыянтом — залежыць ад таго, што падходзіць вашай структуре: express-validator падходзіць для проектаў, якія вже пабудаваны на абмежэннях Express, Zod падходзіць для кодавых баз, якія прыоритэтна выкарыстоўваюць TypeScript і базуюцца на схемах, а Joi ёсць зрэлым універсальным альтернатыўным варыянтом. Не існуе універсальна правильнага выбору; ён залежыць ад архітэктуры вашага прыемніка.
Незалежна адзінам з выбраных вас інструментаў, адзіныя супермоці express-validator — это яго вбудованыя верыфікаторы, адзінственнікі дадзеных, спецыяльныя та асінхронныя верыфікаторы, яго модэль мідлвэра і централізаваная обработка адзінакоў. Чыстая паеўздача запыткаў у Express зазвычай выглядае так:
Request
↓
Validator
↓
Controller
↓
Service
↓
Database
Галоўная мета — не проста паказаць, чы рошчына налягае на адрэс электроннай пашты, а як можно раней адхіліць некоректныя даны, каб рэшта прыкладнення застаўалася чыстай.
Спадневаная літэратура
- Прапозыціі TC39 у 2026 годзе: декоратары, Temporal та Signals — адпаведныя пояснення — практычны аналіз трох прапозыцый TC39 — натыўных декоратараў, API Temporal та Signals — і якія ўплывы яны маюць для разработчыкаў JavaScript та TypeScript на всіх роўнях.