← Назад к вопросам

Что используешь для валидации данных в 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);

Лучшие практики валидации

  1. Валидируй как можно раньше — прямо в middleware
  2. Не дублируй валидацию — используй shared schemas
  3. Дай хорошие error messages — клиенту нужно знать что не так
  4. Используй типы — z.infer<> для синхронизации со схемой
  5. Разделяй по слоям — domain schemas vs presentation DTOs
  6. Логируй валидационные ошибки — помогает в 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));
  }
);