
JavaScript
Tutorial
TypeScript
Zod 4: Guia Prático para Validação de Dados
Aprenda a validar dados com Zod 4 em TypeScript. Guia prático com coerce, enums, safeParse, React Hook Form e as novidades mais rápidas da biblioteca.
O Zod é uma biblioteca de validação de esquemas que gera tipos TypeScript automaticamente. Com o Zod 4, ela ficou 14x mais rápida e muito mais poderosa. Vou te mostrar tudo que você precisa saber para usar no dia a dia.
Sumário
- Instalação
- Conceitos Básicos
- Coerce - Conversão Automática
- Validações de String
- Validações de Número
- Enum - Valores Específicos
- Arrays
- Optional vs Nullable vs Nullish
- Formulários com React Hook Form
- Validações Customizadas
- Transformações
- Valores Padrão
- Parse vs SafeParse
- Reutilização de Schemas
- Novidades do Zod 4
- Métodos Úteis para Objetos
- Dicas Importantes
- Conclusão
Instalação
npm install zod@^4.0.0
Conceitos Básicos
Tipos Primitivos
import { z } from 'zod';
// Básicos
z.string();
z.number();
z.boolean();
z.date();
// Usando
const name = z.string();
name.parse("João"); // ✅ "João"
name.parse(123); // ❌ Erro!
Objetos
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().min(18)
});
// Validando
const user = userSchema.parse({
name: "Maria",
email: "maria@email.com",
age: 25
}); // ✅ Funciona!
// Gerando tipo automaticamente
type User = z.infer<typeof userSchema>;
// { name: string; email: string; age: number }
Coerce - Conversão Automática
Problema: Formulários HTML sempre retornam strings, mas você precisa de números.
// ❌ Quebra - input HTML retorna "25" (string)
const badSchema = z.object({
age: z.number()
});
// ✅ Funciona - converte "25" para 25 automaticamente
const goodSchema = z.object({
age: z.coerce.number()
});
// Outros exemplos úteis
z.coerce.number() // "123" → 123
z.coerce.boolean() // "true" → true
z.coerce.date() // "2024-01-01" → new Date()
Validações de String
// Validações básicas
z.string().min(3, "Muito curto")
z.string().max(100, "Muito longo")
z.string().email("Email inválido")
z.string().url("URL inválida")
// Formatação automática
z.string().trim() // Remove espaços
z.string().toLowerCase() // Converte para minúsculo
z.string().toUpperCase() // Converte para maiúsculo
// Exemplo completo
const passwordSchema = z.string()
.min(8, "Mínimo 8 caracteres")
.regex(/[A-Z]/, "Deve ter maiúscula")
.regex(/[0-9]/, "Deve ter número");
Validações de Número
z.number().positive() // > 0
z.number().min(0) // >= 0
z.number().max(100) // <= 100
z.number().int() // Inteiro
z.number().multipleOf(5) // Múltiplo de 5
// Exemplo prático
const priceSchema = z.coerce.number()
.positive("Preço deve ser positivo")
.max(10000, "Preço muito alto");
Enum - Valores Específicos
// Opções limitadas
const statusSchema = z.enum(["pending", "approved", "rejected"]);
// Em objetos
const orderSchema = z.object({
id: z.string(),
status: z.enum(["pending", "shipped", "delivered"]),
priority: z.enum(["low", "medium", "high"])
});
Arrays
// Array de strings
z.array(z.string())
// Array de objetos
z.array(z.object({
name: z.string(),
age: z.number()
}))
// Com validações
z.array(z.string())
.min(1, "Deve ter pelo menos 1 item")
.max(10, "Máximo 10 itens")
Optional vs Nullable vs Nullish
A diferença que confunde muita gente!
const schema = z.object({
// OPCIONAL: pode não existir
age: z.number().optional(),
// NULLABLE: pode ser null (mas deve existir)
avatar: z.string().nullable(),
// NULLISH: pode ser undefined OU null
bio: z.string().nullish()
});
// Exemplos válidos:
schema.parse({
avatar: null, // ✅ nullable aceita null
bio: undefined // ✅ nullish aceita undefined
// age não precisa existir
});
Resumo rápido:
.optional()
= pode serundefined
ou não existir.nullable()
= pode sernull
(mas deve existir).nullish()
= pode serundefined
ounull
Formulários com React Hook Form
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
const formSchema = z.object({
name: z.string().min(1, "Nome obrigatório"),
email: z.string().email("Email inválido"),
age: z.coerce.number().min(18, "Maior de idade"),
type: z.enum(["user", "admin"])
});
type FormData = z.infer<typeof formSchema>;
function MyForm() {
const form = useForm<FormData>({
resolver: zodResolver(formSchema)
});
const onSubmit = (data: FormData) => {
console.log(data); // Dados já validados!
};
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* seus campos aqui */}
</form>
);
}
Validações Customizadas
// Validação simples
const passwordSchema = z.string()
.refine(
(password) => password.includes("@") || password.includes("#"),
{ message: "Deve conter @ ou #" }
);
// Validação entre campos
const signupSchema = z.object({
password: z.string().min(8),
confirmPassword: z.string()
})
.refine(
(data) => data.password === data.confirmPassword,
{ message: "Senhas não conferem", path: ["confirmPassword"] }
);
Transformações
// Limpar e formatar dados
const userSchema = z.object({
name: z.string().trim().toLowerCase(),
email: z.string().email().toLowerCase(),
age: z.coerce.number()
});
// Input: { name: " JOÃO ", email: "JOAO@EMAIL.COM", age: "25" }
// Output: { name: "joão", email: "joao@email.com", age: 25 }
Valores Padrão
// Valor padrão simples
z.string().default("não informado")
// Valor padrão com função
z.date().default(() => new Date())
// Exemplo prático
const configSchema = z.object({
theme: z.enum(["light", "dark"]).default("light"),
notifications: z.boolean().default(true)
});
Parse vs SafeParse
// parse() - joga erro se inválido
try {
const result = schema.parse(data);
console.log(result);
} catch (error) {
console.log(error.errors);
}
// safeParse() - retorna success/error
const result = schema.safeParse(data);
if (result.success) {
console.log(result.data);
} else {
console.log(result.error.errors);
}
Reutilização de Schemas
// Schema base
const addressSchema = z.object({
street: z.string(),
city: z.string(),
zipCode: z.string()
});
// Reutilizando
const userSchema = z.object({
name: z.string(),
homeAddress: addressSchema,
workAddress: addressSchema.optional()
});
Novidades do Zod 4
Formatos de String no Nível Superior
// Agora você pode usar direto (mais limpo)
z.email()
z.url()
z.uuid()
z.ipv4()
z.ipv6()
// Ao invés de (ainda funciona, mas deprecated)
z.string().email()
z.string().url()
Template Literals
// Validar padrões específicos
const cssUnit = z.templateLiteral([z.number(), z.enum(["px", "em", "rem"])]);
// Aceita: "10px", "1.5em", "2rem"
const greeting = z.templateLiteral(["hello, ", z.string()]);
// Aceita: "hello, world", "hello, João"
Stringbool - Para Variáveis de Ambiente
const envSchema = z.object({
DEBUG: z.stringbool(), // converte "true"/"false" para boolean
PORT: z.coerce.number()
});
// Aceita: "true", "1", "yes", "on" → true
// Aceita: "false", "0", "no", "off" → false
Métodos Úteis para Objetos
const userSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string(),
password: z.string()
});
// Pegar apenas alguns campos
const publicUser = userSchema.pick({
id: true,
name: true,
email: true
});
// Excluir campos
const userWithoutPassword = userSchema.omit({
password: true
});
// Todos os campos opcionais
const partialUser = userSchema.partial();
Dicas Importantes
- Use
z.coerce
para formulários HTML - Prefira
z.email()
ao invés dez.string().email()
- Use
safeParse
quando não tiver certeza se os dados são válidos - Reutilize schemas para manter consistência
- Combine com React Hook Form para formulários perfeitos
Conclusão
O Zod 4 é uma evolução gigantesca que torna a validação de dados muito mais simples e performática. Com 14x mais velocidade e APIs mais limpas, é a escolha perfeita para qualquer projeto TypeScript.
Principais benefícios:
- ✅ Type Safety automático
- ✅ Coerce para formulários HTML
- ✅ Performance muito superior
- ✅ API limpa e intuitiva
- ✅ Integração perfeita com React Hook Form
Comece com o básico e vá explorando os recursos mais avançados conforme a necessidade!