Aprenda / TypeScript do zero

IntermediárioAula 14 de 1422 min0% da trilha

Projeto guiado: client de API com generics e erro tipado

Junta generics, discriminated union e Zod pra um client de API que não confia cegamente em nada que a rede manda.

Na lição 07 você construiu um buscarApi<T> genérico que confiava cegamente no res.json(). Agora, com discriminated union, generics e Zod já vistos nesta trilha, dá pra construir a versão que checa de verdade o que a API devolveu — antes de tratar aquele dado como confiável.

1. O formato do resultado — Ok<T> | Err

Em vez de lançar exceção, a função devolve um discriminado por kind — o mesmo padrão da lição de enums e discriminated unions, só que batizado Ok/Err, nome comum nesse tipo de API:

type Ok<T> = { kind: "ok"; data: T };
type Err = { kind: "err"; error: string };
type Resultado<T> = Ok<T> | Err;

2. A função genérica, agora validando com Zod

import { z } from "zod";

async function buscarApi<T>(url: string, schema: z.ZodType<T>): Promise<Resultado<T>> {
  try {
    const res = await fetch(url);
    if (!res.ok) {
      return { kind: "err", error: `Erro ${res.status}` };
    }

    const bruto = await res.json();
    const validado = schema.safeParse(bruto);

    if (!validado.success) {
      return { kind: "err", error: "Formato de resposta inesperado" };
    }

    return { kind: "ok", data: validado.data };
  } catch {
    return { kind: "err", error: "Falha de rede" };
  }
}

O generic <T> garante o tipo certo pra quem chama a função; o schema garante que o valor que chega de verdade bate com aquele tipo. Uma coisa não substitui a outra — T some na compilação, só o schema continua de pé quando o código está rodando.

3. Definindo o schema e derivando o tipo

const PedidoSchema = z.object({
  id: z.string(),
  status: z.enum(["pendente", "pago", "cancelado"]),
  total: z.number(),
});

type Pedido = z.infer<typeof PedidoSchema>;

4. Usando — narrowing pelo discriminante kind

async function carregarPedido(id: string) {
  const resultado = await buscarApi(`/api/pedidos/${id}`, PedidoSchema);

  switch (resultado.kind) {
    case "ok":
      return resultado.data; // TS sabe: é um Pedido de verdade, checado
    case "err":
      console.error(resultado.error); // TS sabe: só "error" existe aqui
      return null;
  }
}

Exercício

Escreva uma função buscarUsuario(id: string) que usa buscarApi com um UsuarioSchema ({ nome: string; email: string }) pra buscar /api/usuarios/:id, e trata o Resultado com um switch sobre kind.

Quiz rápido

Nesse client de API, por que buscarApi recebe um schema Zod como parâmetro, além do generic <T>?
No discriminated union Resultado<T> = Ok<T> | Err, qual o papel do campo kind?