Type safety em boundaries: Zod, io-ts e validação runtime
- ⬜🧰 Tipos utilitários (Partial, Pick, Omit...) e quando NÃO usar(TypeScript Profissional)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
O mito: "TypeScript garante que não quebra"
É o engano mais comum de devs júnior em TS. Veja:
interface User { id: string; email: string; age: number; }
const res = await fetch('/api/users/1');
const user: User = await res.json(); // 👀 PERIGOSO
console.log(user.email.toUpperCase()); // 💥 se email vier null em runtime O cast as User (implícito no : User) é uma asserção falsa no runtime. A API pode mudar; campo pode vir null; servidor pode ter bug. TS não sabe de nada disso.
Todo boundary (API, form, localStorage, URL param, env var) é uma fronteira onde tipos TS param de valer. Precisa de validação runtime.
Zod: schema é código, tipo é derivado
import { z } from 'zod';
const UserSchema = z.object({
id: z.string(),
email: z.string().email(),
age: z.number().int().nonnegative(),
});
// Tipo inferido — ÚNICA fonte de verdade
type User = z.infer<typeof UserSchema>;
async function fetchUser(id: string): Promise<User | null> {
const res = await fetch(`/api/users/${id}`);
const raw = await res.json();
const parsed = UserSchema.safeParse(raw);
if (!parsed.success) {
console.error('API shape divergiu:', parsed.error);
return null;
}
return parsed.data; // agora é User com garantia runtime
}Padrão: parse em toda porta de entrada
No FFV Academy, este site usa Zod em:
- → validado
- → derivado
- → sem validar
- Rede e entrega
- Fora da AWS
- Banco de dados
- Gestão e governança
- Segurança e identidade
- Conceito de arquitetura
- Compute
A linha vertical do desenho é a fronteira: à esquerda, promessas; à direita, garantias. Todo o trabalho de tipagem só vale de verdade depois de alguém ter validado em tempo de execução.
- 1 · Anotar não é verificar. Declarar o tipo de uma resposta é uma promessa sua. Nada em tempo de execução confere — e o build fica verde enquanto o campo chega nulo.
- 2 · Toda porta de entrada conta. Não é só a API. Formulário, armazenamento local, parâmetro de URL e variável de ambiente entram sem passar por nenhuma checagem sua.
- 3 · Validar na borda concentra o problema num lugar. Depois dela, o tipo estático finalmente corresponde à realidade — e o resto do código pode confiar nele.
- 4 · O tipo sai do schema, não ao lado dele. Mantidos em paralelo, os dois divergem: alguém acrescenta o campo em um e esquece o outro. Derivar elimina a possibilidade.
- 5 · O estado restaurado é a porta esquecida. Dado recuperado do armazenamento local depois de um refresh foi escrito por uma versão ANTIGA do seu código. É entrada externa, ainda que ninguém a tenha digitado.
- 6 · Falhar na borda é o comportamento desejado. Erro claro no ponto de entrada é muito melhor que valor estranho atravessando três camadas até estourar longe da causa.
- — JSON importado pelo usuário
- — param da URL
- — leitura de localStorage (pode ter tamper)
- — attempts restaurados após refresh
Cada um desses é um "boundary". Zod garante que a partir dali, o código roda em terreno sólido.
`const user: User = await res.json()` compila sem erro. Por que isso é perigoso?
Zod vs io-ts vs Valibot: escolha
Zod: DX mais amiga, ecossistema enorme, bundle maior (~25kb). Default recomendado.Valibot: API parecida, bundle ~10x menor (tree-shakable). Boa escolha pra frontend crítico de bundle.io-ts: baseado em fp-ts, academicamente correto mas DX pesada. Use só se o time já pratica FP.
No Next.js com static export, bundle importa. Se este site fosse refeito hoje, provavelmente migrava pra Valibot. Por ora, Zod é o pragmático.
Perguntas frequentes
❓ Onde a validação em execução é obrigatória?
❓ Validar duplica o trabalho de declarar o tipo?
❓ O que fazer quando a validação falha em produção?
Fixando
No padrão com Zod, por que o tipo é derivado do schema com `z.infer` em vez de declarado à parte?
Quais entradas contam como boundary e exigem `parse`?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…