Zod: validação de dados com inferência de tipos
1. Introdução ao Zod e seus fundamentos
Um dos maiores desafios no desenvolvimento com TypeScript é a desconexão entre tipos em tempo de compilação e dados em tempo de execução. Enquanto o TypeScript nos protege durante o desenvolvimento, em runtime os dados vindos de APIs, formulários ou arquivos podem ser qualquer coisa. É aí que entra o Zod.
Zod é uma biblioteca de validação de esquemas que declara schemas e automaticamente infere os tipos TypeScript correspondentes. Diferente de outras soluções, Zod coloca a inferência de tipos como característica central, eliminando a duplicação entre validação e tipagem.
Para começar, instale o Zod:
npm install zod
O exemplo mais básico demonstra o poder da biblioteca:
import { z } from 'zod';
const UserSchema = z.object({
nome: z.string(),
idade: z.number(),
email: z.string().email()
});
// Tentativa de parse com dados inválidos
try {
UserSchema.parse({ nome: "João", idade: "25", email: "invalido" });
} catch (error) {
console.error(error.errors);
// [
// { code: 'invalid_type', expected: 'number', received: 'string', path: ['idade'] },
// { code: 'invalid_string', validation: 'email', path: ['email'] }
// ]
}
2. Esquemas básicos e composição
Zod oferece validação para todos os tipos primitivos e estruturas complexas:
// Tipos primitivos com refinamentos
const nomeSchema = z.string().min(3).max(100);
const idadeSchema = z.number().int().positive().max(150);
const ativoSchema = z.boolean();
const dataSchema = z.date().min(new Date("2020-01-01"));
// Estruturas compostas
const tagsSchema = z.array(z.string().max(20));
const coordenadaSchema = z.tuple([z.number(), z.number()]);
// União e interseção
const resultadoSchema = z.union([z.string(), z.number()]);
const completoSchema = z.intersection(
z.object({ nome: z.string() }),
z.object({ idade: z.number() })
);
// Objetos aninhados e reutilização
const EnderecoSchema = z.object({
rua: z.string(),
cidade: z.string(),
uf: z.string().length(2)
});
const UsuarioCompletoSchema = z.object({
nome: z.string(),
endereco: EnderecoSchema,
contatos: z.array(z.object({
tipo: z.enum(["email", "telefone"]),
valor: z.string()
}))
});
3. Inferência de tipos com z.infer
A grande inovação do Zod é a capacidade de extrair tipos TypeScript diretamente dos schemas:
const UsuarioSchema = z.object({
id: z.string().uuid(),
nome: z.string().min(3),
email: z.string().email(),
idade: z.number().int().optional(),
criadoEm: z.date()
});
// Inferência automática do tipo
type Usuario = z.infer<typeof UsuarioSchema>;
// Equivalente manual (que não precisamos mais escrever!)
// type Usuario = {
// id: string;
// nome: string;
// email: string;
// idade?: number;
// criadoEm: Date;
// };
// Uso prático: um schema como fonte única da verdade
function processarUsuario(dados: unknown): Usuario {
return UsuarioSchema.parse(dados);
}
Isso elimina completamente a duplicação. Qualquer alteração no schema reflete automaticamente no tipo.
4. Validação segura: parse, safeParse e tratamento de erros
Zod oferece duas abordagens para validação:
// parse() - lança exceção
try {
const usuario = UsuarioSchema.parse(dadosBrutos);
// continua com dados validados
} catch (error) {
if (error instanceof z.ZodError) {
error.errors.forEach(err => {
console.log(`Campo: ${err.path.join('.')}`);
console.log(`Erro: ${err.message}`);
console.log(`Código: ${err.code}`);
});
}
}
// safeParse() - retorna objeto com status
const resultado = UsuarioSchema.safeParse(dadosBrutos);
if (resultado.success) {
const usuario: Usuario = resultado.data;
// dados validados e tipados
} else {
const erros = resultado.error.flatten();
console.log(erros.fieldErrors);
// { nome: ['String must contain at least 3 character(s)'], email: ['Invalid email'] }
}
safeParse é ideal para APIs e formulários onde queremos tratar erros sem exceções.
5. Transformações e refinamentos customizados
O Zod permite modificar dados durante a validação e criar regras complexas:
const UsuarioSchema = z.object({
nome: z.string().transform(val => val.trim()),
email: z.string().email().transform(val => val.toLowerCase()),
senha: z.string().min(8),
confirmacaoSenha: z.string()
}).refine(data => data.senha === data.confirmacaoSenha, {
message: "Senhas não conferem",
path: ["confirmacaoSenha"]
});
// Transformações com superRefine para validações complexas
const PedidoSchema = z.object({
itens: z.array(z.object({
produto: z.string(),
quantidade: z.number().positive(),
precoUnitario: z.number().positive()
})),
desconto: z.number().min(0).max(100).optional()
}).superRefine((data, ctx) => {
const total = data.itens.reduce(
(sum, item) => sum + item.quantidade * item.precoUnitario,
0
);
if (data.desconto && data.desconto > total * 0.5) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Desconto não pode exceder 50% do total",
path: ["desconto"]
});
}
});
6. Integração com APIs e serviços externos
Validar dados de APIs externas é um caso de uso crítico:
// Definindo schema para resposta de API
const ApiResponseSchema = z.object({
status: z.enum(["success", "error"]),
data: z.unknown(),
pagination: z.object({
page: z.number().int().positive(),
totalPages: z.number().int().positive(),
totalItems: z.number().int().nonnegative()
}).optional()
});
// Usando com fetch
async function fetchUsuario(id: string): Promise<Usuario> {
const response = await fetch(`https://api.exemplo.com/usuarios/${id}`);
const dadosBrutos: unknown = await response.json();
const validado = ApiResponseSchema.parse(dadosBrutos);
if (validado.status === "error") {
throw new Error("API retornou erro");
}
// Validando dados específicos do usuário
return UsuarioSchema.parse(validado.data);
}
// Em um endpoint Express
import express from 'express';
const app = express();
app.post('/usuarios', (req, res) => {
const resultado = UsuarioSchema.safeParse(req.body);
if (!resultado.success) {
return res.status(400).json({
error: "Dados inválidos",
detalhes: resultado.error.flatten().fieldErrors
});
}
const usuario = resultado.data; // Tipado como Usuario
// Processar...
res.status(201).json(usuario);
});
7. Zod no ecossistema TypeScript moderno
O Zod se integra perfeitamente com frameworks modernos:
// tRPC - validação automática de inputs e outputs
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
const appRouter = t.router({
criarUsuario: t.procedure
.input(UsuarioSchema)
.mutation(async ({ input }) => {
// input é tipado automaticamente como Usuario
return await db.usuario.criar(input);
})
});
// React Hook Form com resolver Zod
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
function CadastroForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(UsuarioSchema)
});
return (
<form onSubmit={handleSubmit(data => {
// data é tipado como Usuario
console.log(data);
})}>
<input {...register('nome')} />
{errors.nome && <span>{errors.nome.message}</span>}
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
<button type="submit">Cadastrar</button>
</form>
);
}
8. Considerações finais e boas práticas
Zod se destaca quando comparado a outras bibliotecas:
- Yup: Mais verboso, menos integração com TypeScript
- Joi: Excelente para Node.js, mas sem inferência nativa
- io-ts: Mais puro funcionalmente, mas curva de aprendizado maior
Para performance, valide dados em lote quando possível:
// Em vez de validar individualmente
const usuarios = dados.map(u => UsuarioSchema.parse(u));
// Valide em lote com array
const usuariosValidados = z.array(UsuarioSchema).parse(dados);
Boas práticas finais:
- Mantenha schemas em arquivos separados por domínio
- Version seus schemas junto com as migrações de banco
- Teste schemas complexos com dados de borda
- Use z.infer para gerar tipos, nunca o contrário
Zod não é apenas uma biblioteca de validação — é a ponte que conecta a segurança do sistema de tipos do TypeScript com a realidade imprevisível dos dados em tempo de execução.
Referências
- Documentação Oficial do Zod — Guia completo com todos os métodos, exemplos e casos de uso da biblioteca
- Zod: Validação de dados com inferência de tipos no TypeScript - Rocketseat — Artigo prático mostrando implementação em projetos reais
- Using Zod with React Hook Form — Tutorial oficial de integração entre React Hook Form e Zod
- tRPC + Zod: End-to-end Type Safety — Documentação oficial do tRPC sobre uso de validadores como Zod
- TypeScript: Validating API Responses with Zod — Tutorial avançado sobre validação de respostas de APIs com Zod
- Zod vs Yup: Which Validation Library Should You Choose? — Comparação detalhada entre Zod e Yup com benchmarks de performance
- Effective Error Handling with Zod — Guia da Prisma sobre tratamento de erros com Zod em aplicações TypeScript