← Назад к вопросам
Что используешь для валидации данных в Express.js?
1.0 Junior🔥 181 комментариев
#Node.js и JavaScript#Фреймворки и библиотеки
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Валидация данных в Express.js: лучшие практики
Валидация данных — это критичный слой между пользовательским вводом и бизнес-логикой. За годы я использовал разные подходы, и вот мой текущий стек.
Мой выбор: Zod + middleware
Я использую Zod как основной инструмент валидации:
// src/domain/User/schemas.ts
import { z } from 'zod';
export const CreateUserSchema = z.object({
email: z.string().email('Invalid email format'),
password: z.string().min(8, 'Password must be at least 8 characters'),
firstName: z.string().min(2, 'First name too short'),
lastName: z.string().min(2, 'Last name too short'),
age: z.number().int().min(13).max(120).optional(),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
Почему Zod?
Плюсы:
- Full TypeScript support с z.infer<typeof T>
- Declarative schema definition
- Хорошие error messages по умолчанию
- Можно использовать .refine() для custom validators
- Lightweight
Custom middleware для валидации
// src/presentation/http/middleware/validateRequest.ts
import { Request, Response, NextFunction } from 'express';
import { z, ZodSchema } from 'zod';
interface ValidateOptions {
body?: ZodSchema;
params?: ZodSchema;
query?: ZodSchema;
}
export function validateRequest(options: ValidateOptions) {
return (req: Request, res: Response, next: NextFunction) => {
try {
if (options.body) {
req.body = options.body.parse(req.body);
}
if (options.params) {
req.params = options.params.parse(req.params);
}
if (options.query) {
req.query = options.query.parse(req.query);
}
next();
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
error: 'Validation failed',
details: error.errors.map(e => ({
path: e.path.join("."),
message: e.message,
code: e.code,
}))
});
}
next(error);
}
};
}
Использование в Express controller'ах
// src/presentation/http/UserController.ts
import express from 'express';
import { validateRequest } from './middleware/validateRequest';
import { CreateUserSchema } from '@/domain/User/schemas';
import { CreateUserUseCase } from '@/application/user/CreateUserUseCase';
const router = express.Router();
const createUserUseCase = new CreateUserUseCase(...);
router.post(
'/users',
validateRequest({ body: CreateUserSchema }),
async (req: express.Request, res: express.Response) => {
try {
const user = await createUserUseCase.execute(req.body);
res.status(201).json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
}
);
Сложные валидации с .refine()
// Валидация что password и confirmPassword совпадают
const PasswordSchema = z.object({
password: z.string().min(8),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
});
// Проверка что email еще не зарегистрирован
const RegisterSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
}).refine(async (data) => {
const existing = await db.users.findOne({ email: data.email });
return !existing;
}, {
message: 'Email already registered',
path: ['email'],
});
const user = await RegisterSchema.parseAsync(req.body);
Валидация Path параметров
// src/domain/User/schemas.ts
export const UserIdParamSchema = z.object({
id: z.string().uuid('Invalid user ID format'),
});
// В controller'е
router.get(
'/users/:id',
validateRequest({ params: UserIdParamSchema }),
async (req: express.Request, res: express.Response) => {
const { id } = req.params;
const user = await findUserById(id);
res.json(user);
}
);
Валидация query параметров
// src/domain/Common/schemas.ts
export const PaginationSchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
sort: z.enum(['asc', 'desc']).default('desc'),
});
// В controller'е
router.get(
'/users',
validateRequest({ query: PaginationSchema }),
async (req: express.Request, res: express.Response) => {
const { page, limit, sort } = req.query;
const users = await findUsers({
skip: (page - 1) * limit,
take: limit,
orderBy: sort,
});
res.json(users);
}
);
Глобальный error handler для валидации
// src/presentation/http/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
export function globalErrorHandler(
error: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof z.ZodError) {
return res.status(400).json({
code: 'VALIDATION_ERROR',
message: 'Request validation failed',
errors: error.errors.map(err => ({
field: err.path.join("."),
message: err.message,
code: err.code,
}))
});
}
if (error instanceof Error) {
return res.status(500).json({
code: 'INTERNAL_SERVER_ERROR',
message: error.message,
});
}
res.status(500).json({
code: 'UNKNOWN_ERROR',
message: 'An unknown error occurred',
});
}
// В app.ts
app.use(globalErrorHandler);
Лучшие практики валидации
- Валидируй как можно раньше — прямо в middleware
- Не дублируй валидацию — используй shared schemas
- Дай хорошие error messages — клиенту нужно знать что не так
- Используй типы — z.infer<> для синхронизации со схемой
- Разделяй по слоям — domain schemas vs presentation DTOs
- Логируй валидационные ошибки — помогает в debug'е
// Хорошо: разделение схем
// domain/User/User.ts - основная сущность
// domain/User/schemas.ts - валидационные схемы
// presentation/http/dtos/ - transfer objects для API
// Тогда в controller'е:
router.post('/users',
validateRequest({ body: UserCreateSchema }),
async (req, res) => {
const user = await usecase.execute(req.body);
res.json(UserResponseDTO.from(user));
}
);