<h2>O que é Zod e por que você precisa</h2>
<p>Zod é uma biblioteca TypeScript-first para validação de schemas e inferência de tipos. Ela resolve um problema fundamental no desenvolvimento moderno: garantir que dados em tempo de execução correspondam aos tipos que você declarou em TypeScript. Enquanto TypeScript oferece segurança de tipo em tempo de compilação, os dados que chegam da API, formulários ou banco de dados precisam ser validados em tempo de execução. Zod preenche essa lacuna de forma elegante e performática.</p>
<p>A principal vantagem do Zod sobre alternativas como Joi ou Yup é sua abordagem orientada a tipos. Você define um schema uma única vez e Zod automaticamente infere o tipo TypeScript correspondente. Não há duplicação de código, não há schemas separados de tipos — tudo vem de um único source of truth. Isso reduz bugs e torna o código mais mantível em grandes aplicações.</p>
<h2>Conceitos Fundamentais</h2>
<h3>Entendendo Schemas</h3>
<p>Um schema no Zod é uma descrição estruturada de como os dados devem se parecer. Você começa com tipos primitivos como <code>z.string()</code>, <code>z.number()</code>, <code>z.boolean()</code> e constrói estruturas mais complexas a partir deles. Cada schema é uma instância de uma classe que sabe validar dados e produzir mensagens de erro detalhadas.</p>
<p>Quando você chama <code>.parse()</code> em um schema, Zod valida os dados. Se falharem, lança uma exceção com informações sobre o que deu errado. Existe também <code>.safeParse()</code> que retorna um objeto com a propriedade <code>success</code> e os dados ou erros, sem lançar exceção. Essa flexibilidade permite que você escolha como lidar com validações falhadas.</p>
<pre><code class="language-typescript">import { z } from 'zod';
// Schema simples
const usuarioSchema = z.object({
nome: z.string().min(3, 'Nome deve ter no mínimo 3 caracteres'),
email: z.string().email('Email inválido'),
idade: z.number().int().positive('Idade deve ser um número positivo'),
ativo: z.boolean().default(true),
});
// Validação com parse (lança erro)
const usuario = usuarioSchema.parse({
nome: 'João Silva',
email: 'joao@example.com',
idade: 28,
});
// Validação com safeParse (retorna resultado)
const resultado = usuarioSchema.safeParse({
nome: 'Jo',
email: 'email-invalido',
idade: -5,
});
if (!resultado.success) {
console.log(resultado.error.errors);
// [
// { message: 'Nome deve ter no mínimo 3 caracteres', ... },
// { message: 'Email inválido', ... },
// { message: 'Idade deve ser um número positivo', ... }
// ]
}</code></pre>
<h3>Inferência de Tipos</h3>
<p>A inferência de tipos é onde Zod brilha. Ao invés de escrever uma interface TypeScript manualmente e depois um schema Zod que replique essa interface, você escreve apenas o schema e deixa Zod inferir o tipo automaticamente usando <code>z.infer<typeof></code>. Isso garante que seu tipo sempre corresponde exatamente ao seu schema.</p>
<p>Quando você precisa usar o tipo em anotações TypeScript, basta usar <code>z.infer</code>. Isso cria uma relação bidirecional automática: mude o schema, e o tipo muda junto. Não há chance de sincronização manual falhar.</p>
<pre><code class="language-typescript">import { z } from 'zod';
const usuarioSchema = z.object({
id: z.number(),
nome: z.string(),
email: z.string().email(),
telefone: z.string().optional(),
permissoes: z.array(z.enum(['leitura', 'escrita', 'admin'])),
});
// Infer o tipo automaticamente
type Usuario = z.infer<typeof usuarioSchema>;
// Agora Usuario é:
// {
// id: number;
// nome: string;
// email: string;
// telefone?: string | undefined; // permissoes: ('leitura' | 'escrita' | 'admin')[];
// }
function processarUsuario(usuario: Usuario) {
console.log(Processando ${usuario.nome} com email ${usuario.email});
}
const dados = usuarioSchema.parse({
id: 1,
nome: 'Maria',
email: 'maria@example.com',
permissoes: ['leitura', 'escrita'],
});
processarUsuario(dados); // TypeScript sabe que dados é Usuario</code></pre>
<h2>Técnicas Avançadas de Validação</h2>
<h3>Refinamentos Customizados</h3>
<p>Nem toda validação pode ser expressa com os validadores built-in do Zod. Para lógica complexa ou validações entre campos, você usa <code>.refine()</code> ou <code>.superRefine()</code>. O método <code>refine</code> recebe uma função que retorna um booleano, enquanto <code>superRefine</code> oferece mais controle sobre as mensagens de erro.</p>
<p>Refinamentos são poderosos para validações que envolvem múltiplos campos ou lógica de negócio. Por exemplo, validar se a data de fim é posterior à data de início, ou se uma senha contém caracteres especiais. Eles são executados após as validações primitivas passarem.</p>
<pre><code class="language-typescript">import { z } from 'zod';
const agendamentoSchema = z
.object({
dataInicio: z.coerce.date(),
dataFim: z.coerce.date(),
descricao: z.string().min(5),
})
.refine((data) => data.dataFim > data.dataInicio, {
message: 'Data de fim deve ser posterior à data de início',
path: ['dataFim'], // Indica qual campo tem o erro
});
const senhaSchema = z
.string()
.min(8, 'Senha deve ter no mínimo 8 caracteres')
.superRefine((val, ctx) => {
if (!/[A-Z]/.test(val)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Deve conter pelo menos uma letra maiúscula',
});
}
if (!/[0-9]/.test(val)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Deve conter pelo menos um número',
});
}
if (!/[!@#$%^&*]/.test(val)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Deve conter pelo menos um caractere especial',
});
}
});
const agendamento = agendamentoSchema.parse({
dataInicio: '2024-01-15',
dataFim: '2024-01-16',
descricao: 'Reunião importante',
});
const resultado = senhaSchema.safeParse('abc123');
// Vai falhar com todas as três mensagens customizadas</code></pre>
<h3>Transformações</h3>
<p>Transformações permitem modificar o valor após validação bem-sucedida. Use <code>.transform()</code> para normalizar dados: converter strings para maiúsculas, parsear JSON, processar datas, remover espaços em branco. Transformações são aplicadas apenas após todas as validações passarem e garantem que o dado retornado é consistente com o que você espera.</p>
<pre><code class="language-typescript">import { z } from 'zod';
const usuarioRegistroSchema = z.object({
nome: z.string().transform((val) => val.trim().toLowerCase()),
email: z
.string()
.email()
.transform((val) => val.toLowerCase()),
dataNascimento: z.coerce.date().transform((date) => {
const hoje = new Date();
return Math.floor((hoje.getTime() - date.getTime()) / (365.25 24 60 60 1000));
}),
preferencias: z
.string()
.optional()
.transform((val) => {
if (!val) return {};
try {
return JSON.parse(val);
} catch {
return {};
}
}),
});
const resultado = usuarioRegistroSchema.parse({
nome: ' João Silva ',
email: 'JOAO@EXAMPLE.COM',
dataNascimento: '1995-05-20',
preferencias: '{"tema":"escuro","idioma":"pt"}',
});
console.log(resultado);
// {
// nome: 'joão silva',
// email: 'joao@example.com',
// dataNascimento: 28,
// preferencias: { tema: 'escuro', idioma: 'pt' }
// }</code></pre>
<h2>Casos de Uso Práticos</h2>
<h3>Validação de APIs e Formulários</h3>
<p>Em uma API real, você precisa validar dados que chegam de clientes. Zod se integra perfeitamente com frameworks como Express, Fastify e Next.js. Defina um schema, valide o request body e tenha segurança de tipo garantida em todo o handler.</p>
<pre><code class="language-typescript">import { z } from 'zod';
import express from 'express';
const app = express();
app.use(express.json());
// Schema para criar um post
const criarPostSchema = z.object({
titulo: z.string().min(5).max(200),
conteudo: z.string().min(20).max(10000),
tags: z.array(z.string()).max(5),
publicado: z.boolean().default(false),
});
type CriarPost = z.infer<typeof criarPostSchema>;
app.post('/posts', (req, res) => {
const resultado = criarPostSchema.safeParse(req.body);
if (!resultado.success) {
return res.status(400).json({
erros: resultado.error.flatten(),
});
}
const post: CriarPost = resultado.data;
// Aqui você tem certeza absoluta que post tem a estrutura correta
console.log(Post criado: ${post.titulo});
res.json({ id: 1, ...post });
});</code></pre>
<h3>Configuração de Ambientes</h3>
<p>Aplicações precisam carregar e validar variáveis de ambiente. Zod garante que você tem todas as variáveis necessárias e com os tipos corretos antes que a aplicação inicie.</p>
<pre><code class="language-typescript">import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
PORT: z.coerce.number().int().positive().default(3000),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
API_TIMEOUT: z.coerce.number().int().positive().default(30000),
LOG_LEVEL: z
.enum(['error', 'warn', 'info', 'debug'])
.default('info'),
});
// No início da sua aplicação
const env = envSchema.parse(process.env);
// Agora env é tipado e você sabe que tem todos os valores necessários
export const config = {
nodeEnv: env.NODE_ENV,
port: env.PORT,
databaseUrl: env.DATABASE_URL,
jwtSecret: env.JWT_SECRET,
apiTimeout: env.API_TIMEOUT,
logLevel: env.LOG_LEVEL,
};</code></pre>
<h3>Composição de Schemas</h3>
<p>Em projetos grandes, você raramente escreve um schema único gigante. Zod permite compor schemas menores em estruturas maiores. Use <code>.merge()</code>, <code>.extend()</code> ou neste em objetos maiores. Isso mantém o código DRY e reutilizável.</p>
<pre><code class="language-typescript">import { z } from 'zod';
// Schemas base reutilizáveis
const baseSchema = z.object({
id: z.number().int().positive(),
criadoEm: z.coerce.date().default(() => new Date()),
atualizadoEm: z.coerce.date(),
});
const usuarioSchema = baseSchema.extend({
nome: z.string().min(3),
email: z.string().email(),
ativo: z.boolean().default(true),
});
const postSchema = baseSchema.extend({
titulo: z.string().min(5),
conteudo: z.string(),
autorId: z.number().int().positive(),
publicado: z.boolean().default(false),
});
// Composição: um usuário com seus posts
const usuarioComPostsSchema = usuarioSchema.extend({
posts: z.array(postSchema),
});
type Usuario = z.infer<typeof usuarioSchema>;
type Post = z.infer<typeof postSchema>;
type UsuarioComPosts = z.infer<typeof usuarioComPostsSchema>;
// Dados complexos agora são tipados e validados perfeitamente
const dados = usuarioComPostsSchema.parse({
id: 1,
nome: 'Alice',
email: 'alice@example.com',
criadoEm: '2024-01-01',
atualizadoEm: '2024-01-15',
posts: [
{
id: 100,
titulo: 'Meu Primeiro Post',
conteudo: 'Conteúdo interessante aqui...',
autorId: 1,
criadoEm: '2024-01-10',
atualizadoEm: '2024-01-15',
},
],
});</code></pre>
<h2>Conclusão</h2>
<p>Zod transforma a forma como você lida com validação em TypeScript. Três pontos principais consolidam esse aprendizado:</p>
<p><strong>Primeiro</strong>, a inferência automática de tipos elimina a duplicação código que plaga projetos que usam validadores tradicionais. Seu schema é fonte única da verdade, reduzindo erros e facilitando manutenção em equipes.</p>
<p><strong>Segundo</strong>, a composição e refinamento de schemas permitem expressar lógica de validação complexa de forma limpa e testável. Não há necessidade de dispersar validação em múltiplos lugares da aplicação.</p>
<p><strong>Terceiro</strong>, a integração com frameworks e padrões modernos (APIs, formulários, variáveis de ambiente) torna Zod uma ferramenta prática do dia a dia, não um overhead teórico. Qualidade de tipo em tempo de execução se torna tão natural quanto type checking estático.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://zod.dev" target="_blank" rel="noopener noreferrer">Documentação Oficial do Zod</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/utility-types.html" target="_blank" rel="noopener noreferrer">Zod no TypeScript Deep Dive - Type Inference</a></li>
<li><a href="https://dev.to/franciscomendes10866/api-validation-in-express-with-zod-6d8" target="_blank" rel="noopener noreferrer">API Validation in Express with Zod - Dev.to</a></li>
<li><a href="https://blog.logrocket.com/using-zod-for-runtime-type-validation-in-typescript/" target="_blank" rel="noopener noreferrer">Runtime Type Validation Patterns - LogRocket</a></li>
<li><a href="https://github.com/colinhacks/zod" target="_blank" rel="noopener noreferrer">Zod GitHub Repository</a></li>
</ul>
<p><!-- FIM --></p>