Pular para o conteúdo

Zod na borda do dado. O que melhora — e o que custa.

Vantagens e desvantagens do Zod com base na documentação oficial: schema, inferência de tipos, safeParse e o aprendizado de validar onde TypeScript não alcança.

Por

~7 min de leitura

Capa: cartão de vidro central com a logo oficial do Zod em branco.

TypeScript tipa o que você modelou. Ele não tipa o JSON que chegou da API, o frontmatter lido do disco nem o payload do formulário. Esses valores entram como unknown de fato — mesmo quando o tipo anotado diz o contrário.

A documentação oficial do Zod descreve a biblioteca como validação TypeScript-first com inferência estática de tipos. Em uma frase: você define o schema uma vez, valida em runtime e extrai o tipo com z.infer. Schema e tipo contam a mesma história.

Na minha opinião, esse é o ganho que importa. Não é “mais uma lib de validação”. É fechar o buraco que o type-checker, sozinho, deixa aberto na borda.

O que o Zod promete (e entrega)

Pela introdução oficial:

  • Zero dependências externas.
  • Roda em Node.js e browsers modernos.
  • Core pequeno — a doc fala em ~2kb gzipped no núcleo.
  • API imutável — métodos devolvem uma nova instância.
  • Interface concisa, útil com TypeScript e também com JS puro.
  • Conversão para JSON Schema embutida.
  • Ecossistema amplo — formulários, APIs, mocks, geradores.

O fluxo básico da guia de uso é direto:

TypeScript
import * as z from 'zod'
 
const Player = z.object({
  username: z.string(),
  xp: z.number(),
})
 
type Player = z.infer<typeof Player>
 
const result = Player.safeParse(input)
 
if (!result.success) {
  // result.error — ZodError com issues granulares
} else {
  // result.data — { username: string; xp: number }
}

.parse() lança ZodError. .safeParse() devolve uma union discriminada. Quando o schema tem transform ou refine assíncrono, entram .parseAsync() / .safeParseAsync(). Para só saber se passa, a doc aponta .validate() — boolean, sem montar erro, mais barato no caminho inválido.

Requisito explícito: "strict": true no tsconfig. Sem isso, você perde parte do contrato que a lib assume.

Principais vantagens

1. Uma fonte de verdade: schema → tipo

Você não mantém interface à mão e validator à parte. O schema é o contrato; z.infer<typeof schema> é o tipo. Quando input e output divergem — por exemplo com .transform() — a doc separa z.input e z.output. Menos mentira entre o que o compilador acredita e o que o runtime aceita.

2. Runtime onde TypeScript some

Tipos são apagados. Zod existe exatamente nesse intervalo: dado não confiável entra, dado tipado sai — ou o erro diz o que falhou. Na prática, isso cobre API remota, localStorage, query string, webhook, arquivo Markdown, formulário.

3. Erros utilizáveis

ZodError traz issues com path, code e mensagem. Dá para mapear campo a campo na UI ou logar o caminho exato do payload. Não é um false opaco.

4. Composição sem reinventar o modelo

Objetos, arrays, unions, discriminated unions, refine, transform, defaults: a API de schemas cobre o dia a dia sem você montar um mini-parser. .extend(), .pick(), .omit(), .partial() espelham o vocabulário de TypeScript — a curva de aprendizado aproveita o que o time já sabe.

5. Ecossistema e integração

A página de ecosystem lista resolvers de formulário, libs de API e pontes “Zod → X / X → Zod”. Na prática, isso reduz atrito: React Hook Form, tRPC e afins já falam o mesmo idioma de schema.

6. Zod 4 como base mais rápida

As release notes do Zod 4 reportam ganhos grandes de parse (string, array, object) e redução pesada de pressão no tsc. Para quem já vive com schemas grandes, isso deixa de ser detalhe de benchmark e vira tempo de CI e de editor.

Desvantagens e custos reais

Vantagem sem trade-off vira marketing. O que eu levo em conta:

1. Schema é código — e código precisa de dono

Cada borda nova pede schema. Sem disciplina, o time tipa o miolo e “deixa para depois” a validação. Ou duplica schema e interface “só por enquanto”. O custo não é o npm install; é manter o contrato vivo.

2. Bundle e superfície não são zero

O core é pequeno, mas a superfície da API é grande. Em cliente agressivo com budget de bytes, a própria doc aponta Zod Mini e lembra que muita gente superestima o peso — ainda assim, é uma decisão consciente, não um “de graça”.

3. Exige TypeScript strict (de verdade)

Se o projeto vive de any e as, Zod vira cerimônia. A doc exige strict. Sem isso, você valida em runtime e continua mentindo no tipo.

4. Transform e refine pedem atenção

.transform() muda o output. Refine assíncrono muda o método de parse. Quem trata todo schema como “objeto estático” se perde. Também é fácil exagerar: transform que vira regra de negócio espalhada no schema deixa o domínio opaco.

5. Não substitui teste nem regra de produto

Zod garante forma e constraints declaradas. Não garante que a regra de negócio está certa, que a mensagem de erro é boa para o usuário ou que a API está disponível. É barreira de dado, não suíte de qualidade.

6. Compilação AOT tem limites

O compile ahead-of-time acelera o caminho válido; no inválido, cai no parser normal para montar o erro. CSP restritiva e alguns formatos de schema não se beneficiam. Útil em hot path — não é mágica universal.

O que ele realmente melhora

O ponto em que eu coloco ênfase: Zod melhora a honestidade da fronteira.

Sem ele (ou equivalente), o padrão comum é:

  1. tipar o JSON “na mão”;
  2. confiar no backend / no arquivo / no form;
  3. descobrir o drift em produção.

Com schema na borda:

  1. o dado sujo para no safeParse;
  2. o restante do app recebe tipo inferido;
  3. o review discute o contrato, não o chute.

Isso melhora três coisas concretas:

  • Confiança no fluxo. Página e componente param de defender formato inválido no miolo.
  • Feedback mais cedo. Erro de shape aparece onde o I/O acontece — serviço, action, loader — não três camadas abaixo.
  • Alinhamento TypeScript ↔ runtime. O mesmo schema alimenta validação e tipo. Menos interface User e userSchema divergindo em silêncio.

No front-end, o ganho aparece em formulário, rota e fetch. Em conteúdo e CMS local, aparece em frontmatter e payload versionado. Em qualquer lugar em que o TypeScript presume e o mundo entrega.

Aprendizados que eu carrego

Lista curta, ancorada no que a doc ensina e no que o uso cobra:

  1. Valide na borda; tipifique o miolo. Schema no I/O. Componente recebe dado já parseado.
  2. Prefira safeParse quando o erro é fluxo. Exception para falha excepcional; result discriminado para input de usuário e dado externo.
  3. Derive o tipo do schema — não o contrário. Se você escreve o tipo primeiro e depois “espelha” no Zod, a duplicação volta.
  4. Modele o domínio no schema, não só o JSON cru. Union discriminada, optional de verdade, refine só quando a regra cabe no contrato.
  5. Trate mensagem de erro como produto. issues granular é matéria-prima; a UX ainda é sua.
  6. Não use Zod para tudo. Constante interna, prop tipada entre Server e Client Components do mesmo app, valor que nunca cruza I/O — TypeScript basta.
  7. Leia a doc oficial como contrato. zod.dev, basics e a API mudam menos que thread de opinião.

Zod não torna o TypeScript “runtime”. Ele torna o runtime honesto com o TypeScript.

O principal aprendizado?

O tipo que você escreve à mão no miolo não protege a borda. O schema que você valida na borda protege o miolo.

Zod brilha quando vira hábito de fronteira: um schema, um tipo inferido, um erro legível. As vantagens — inferência, ecossistema, API concisa — só pagam o preço das desvantagens se o time tratar validação como parte do contrato, não como enfeite opcional.

Comece pelo dado que você menos controla. Defina o schema. Use safeParse. Passe adiante só o que passou. O resto do app agradece — e o type-checker finalmente fala a verdade sobre o que o usuário, a API e o arquivo realmente mandaram.

Leva para a próxima conversa.

Se fez sentido aqui, pode fazer sentido para mais alguém.

Continue lendo

  1. BFF: o que melhora no front — e o que custa manter

    Backend for Frontend não é só mais uma API. Vantagens, desvantagens e o que realmente muda para quem escreve a interface.

    ~7 min de leitura

  2. TypeScript no front-end. Aliado na hora de criar componentes e funcionalidades.

    Como tipar props, estados e fronteiras de dados para o editor e o compilador trabalharem a seu favor — com boas práticas e a opinião de quem usa isso no dia a dia.

    ~7 min de leitura