Validacao Zod

Schemas de validacao com Zod, pipes e middlewares.

Visao geral

Zod e usado para validar e tipar dados de entrada (body, query, params) em todas as rotas da API. O PlazerCLI gera schemas de exemplo e a infraestrutura de validacao especifica para cada framework.

Schemas de exemplo

Gerados em apps/api/src/common/validation/schemas/example.schema.ts:

import { z } from 'zod';

// Criar usuario
export const CreateUserSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email('Email invalido'),
  password: z.string()
    .min(8, 'Senha deve ter pelo menos 8 caracteres')
    .regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
      'Deve conter minuscula, maiuscula e numero'),
});
export type CreateUserDto = z.infer<typeof CreateUserSchema>;

// Login
export const LoginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(1),
});
export type LoginDto = z.infer<typeof LoginSchema>;

// Paginacao
export const PaginationSchema = z.object({
  page: z.coerce.number().int().min(1).default(1),
  limit: z.coerce.number().int().min(1).max(100).default(20),
});
export type PaginationDto = z.infer<typeof PaginationSchema>;

// ID param
export const IdParamSchema = z.object({
  id: z.string().uuid('ID deve ser um UUID valido'),
});
export type IdParamDto = z.infer<typeof IdParamSchema>;

NestJS — ZodValidationPipe

O PlazerCLI gera um PipeTransform customizado para NestJS:

@Injectable()
export class ZodValidationPipe implements PipeTransform {
  constructor(private schema: ZodSchema) {}

  transform(value: unknown) {
    const result = this.schema.safeParse(value);
    if (!result.success) {
      throw new BadRequestException({
        message: 'Erro de validacao',
        errors: this.formatErrors(result.error),
      });
    }
    return result.data;
  }
}

// Uso no controller:
@Post()
create(@Body(new ZodValidationPipe(CreateUserSchema)) dto: CreateUserDto) {
  return this.usersService.create(dto);
}

Express — Middleware validate()

Para Express, tres middlewares sao gerados:

import { validate, validateQuery, validateParams } from '../common/validation/validate.js';
import { CreateUserSchema, PaginationSchema, IdParamSchema } from '../common/validation/schemas/example.schema.js';

// Validar body
router.post('/users', validate(CreateUserSchema), (req, res) => {
  // req.body ja esta validado e tipado
  res.json(req.body);
});

// Validar query params
router.get('/users', validateQuery(PaginationSchema), (req, res) => {
  const { page, limit } = req.validatedQuery;
});

// Validar route params
router.get('/users/:id', validateParams(IdParamSchema), (req, res) => {
  const { id } = req.params;
});

Fastify — preHandler validate()

Para Fastify, os mesmos tres helpers sao gerados como preHandler hooks:

import { validate, validateQuery, validateParams } from '../common/validation/validate.js';
import { CreateUserSchema } from '../common/validation/schemas/example.schema.js';

app.post('/users', {
  preHandler: validate(CreateUserSchema)
}, async (request) => {
  // request.body ja esta validado
  return request.body;
});

Resposta de erro

Quando a validacao falha, o erro retornado segue este formato:

// HTTP 400 Bad Request
{
  "message": "Erro de validacao",
  "errors": {
    "email": ["Email invalido"],
    "password": [
      "Senha deve ter pelo menos 8 caracteres",
      "Deve conter minuscula, maiuscula e numero"
    ]
  }
}