Zod: tipo estático não é validação em runtime
Seu tipo Pedido promete um formato — mas nada confere se a API realmente devolveu isso, até o Zod entrar.
Lá na primeira lição desta trilha: os tipos do TypeScript são apagados na compilação — o navegador (ou o Node) nunca vê um único tipo, só JavaScript puro. Isso tem uma consequência que passa despercebida até morder: nenhum tipo protege seu código de dado errado vindo de fora.
O problema real: dado de fora não é confiável
interface Pedido {
id: string;
status: "pendente" | "pago" | "cancelado";
total: number;
}
async function buscarPedido(id: string): Promise<Pedido> {
const res = await fetch(`/api/pedidos/${id}`);
const dado = await res.json();
return dado as Pedido; // "as" é só uma afirmação — não checa NADA em runtime
}
const pedido = await buscarPedido("1");
pedido.total.toFixed(2);
// se a API mudou e "total" agora vem como string "99.90", isso compila liso
// e quebra em runtime: "pedido.total.toFixed is not a function"O as Pedido não valida o formato do JSON — só diz pro compilador "trate isso como um Pedido, eu prometo que é". Se a promessa for falsa (API mudou, bug no backend, resposta de erro inesperada), o TS não tem como saber: ele já apagou toda essa checagem antes do código rodar.
Zod — validação de verdade, em runtime
Zod é uma biblioteca que descreve o formato esperado de um dado como um schema, e sabe checar, durante a execução, se um valor real bate com esse formato:
import { z } from "zod";
const PedidoSchema = z.object({
id: z.string(),
status: z.enum(["pendente", "pago", "cancelado"]),
total: z.number(),
});
async function buscarPedido(id: string) {
const res = await fetch(`/api/pedidos/${id}`);
const bruto = await res.json();
const resultado = PedidoSchema.safeParse(bruto);
if (!resultado.success) {
console.error("Formato inesperado:", resultado.error);
return null;
}
return resultado.data; // aqui sim, checado de verdade — "total" É number, na prática
}safeParse não lança exceção — devolve um objeto com success, exatamente como o discriminated union de resultado que você já usou nesta trilha. Se o dado real não bater com o schema (campo faltando, tipo errado), você descobre ali, com uma mensagem clara, em vez de um erro genérico três camadas depois.
z.infer — o tipo TypeScript nasce do schema, não o contrário
const PedidoSchema = z.object({
id: z.string(),
status: z.enum(["pendente", "pago", "cancelado"]),
total: z.number(),
});
type Pedido = z.infer<typeof PedidoSchema>;
// { id: string; status: "pendente" | "pago" | "cancelado"; total: number }
// — o tipo é derivado do schema, então os dois nunca ficam dessincronizadosSem z.infer, seria fácil manter uma interface Pedido e um PedidoSchema separados — e um dia mudar só um dos dois. Derivando o tipo do schema, existe uma única fonte de verdade.
Exercício
Escreva um schema Zod UsuarioSchema pra { nome: string; email: string; idade: number } e derive o tipo Usuario a partir dele com z.infer.