//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tipifique suas respostas de API de ponta a ponta, desde chamadas fetch até a renderização de componentes. Use type guards e validação em tempo de execução para preencher a lacuna entre dados de rede sem tipo e seus tipos TypeScript.
// Defina seus tipos de API
type ApiUser = {
id: number;
name: string;
email: string;
role: "admin" | "editor" | "viewer";
};
type ApiResponse<T> = {
data: T;
meta: {
page: number;
totalPages: number;
totalCount: number;
};
};
// Wrapper de fetch type-safe
async function fetchJson<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json() as Promise<T>;
}
// Uso em um componente
function UserList() {
const [result, setResult] = useState<ApiResponse<ApiUser[]> | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchJson<ApiResponse<ApiUser[]>>("/api/users")
.then(setResult)
.catch((err) => setError(err.message));
}, []);
if (error) return <p>Erro: {error}</p>;
if (!result) return <p>Carregando...</p>;
return (
<ul>
{result.data.map((user) => (
<li key={user.id}>{user.name} ({user.role})</li>
))}
</ul>
);
}fetch retorna Response, e response.json() retorna Promise<any>. O cast as Promise<T> informa ao TypeScript qual formato esperar, mas não valida os dados em tempo de execução.fetch (fetchJson<T>) centraliza o tratamento de erros e a tipagem. Cada local de chamada especifica o formato de resposta esperado.Validação em tempo de execução com Zod:
import { z } from "zod";
const ApiUserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
role: z.enum(["admin", "editor", "viewer"]),
});
type ApiUser = z.infer<typeof ApiUserSchema>;
const ApiResponseSchema = <T extends z.ZodType>(dataSchema: T) =>
z.object({
data: dataSchema,
meta: z.object({
page: z.number(),
totalPages: z.number(),
totalCount: z.number(),
}),
});
async function fetchValidated<T>(url: string, schema: z.ZodType<T>): Promise<T> {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const json = await response.json();
return schema.parse(json); // Lança ZodError se a validação falhar
}
// Uso
const result = await fetchValidated(
"/api/users",
ApiResponseSchema(z.array(ApiUserSchema))
);Type guard personalizado:
function isApiUser(value: unknown): value is ApiUser {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value &&
"email" in value &&
"role" in value &&
typeof (value as ApiUser).id === "number" &&
typeof (value as ApiUser).name === "string"
);
}
// Uso
const data: unknown = await response.json();
if (isApiUser(data)) {
console.log(data.name); // TypeScript sabe que data é ApiUser
}Tipagem de resposta de erro:
type ApiError = {
message: string;
code: string;
details?: Record<string, string[]>;
};
type ApiResult<T> =
| { success: true; data: T }
| { success: false; error: ApiError };
async function fetchApi<T>(url: string): Promise<ApiResult<T>> {
try {
const response = await fetch(url);
const json = await response.json();
if (!response.ok) {
return { success: false, error: json as ApiError };
}
return { success: true, data: json as T };
} catch {
return { success: false, error: { message: "Erro de rede", code: "NETWORK" } };
}
}response.json() retorna Promise<any>. O cast as Promise<T> é um compromisso necessário, pois a análise de JSON é inerentemente sem tipo.z.infer<typeof Schema> deriva o tipo TypeScript de um esquema Zod, fornecendo uma única fonte de verdade.unknown em vez de any para dados não validados. Isso força você a refinar ou validar antes de acessar propriedades.as T não oferece segurança em tempo de execução. Se a API mudar seu formato de resposta, seu código falhará silenciosamente até que o acesso a uma propriedade cause um erro.fetch não lança erros em respostas 4xx ou 5xx. Você deve verificar response.ok ou response.status manualmente.Date). Transforme-os em uma camada de mapeamento..data em null, causando erros em tempo de execução.| Abordagem | Prós | Contras |
|---|---|---|
Cast as T | Simples, sem dependências | Sem validação em tempo de execução |
| Validação Zod | Segurança em tempo de execução + compilação, única fonte de verdade | Dependência adicional, sobrecarga de análise |
Type guards (is functions) | Sem dependências, refinamento explícito | Tedioso para tipos complexos, fácil de errar |
| tRPC | Segurança de tipo ponta a ponta, sem tipagem manual | Requer configuração de servidor + cliente |
| Geração de código GraphQL | Tipos gerados a partir do esquema | Etapa de compilação, ecossistema GraphQL necessário |
as Promise<T> para fazer o cast, ou valide com schema.parse(json) do Zod para segurança em tempo de execução.response.ok, captura de erros de rede).const UserSchema = z.object({
id: z.number(),
name: z.string(),
});
type User = z.infer<typeof UserSchema>; // tipo derivado
const data = UserSchema.parse(json); // validação em tempo de execuçãoz.infer<typeof Schema> deriva o tipo TypeScript do esquema.schema.parse(json) lança um ZodError se os dados não corresponderem em tempo de execução.as T é uma afirmação apenas em tempo de compilação, sem segurança em tempo de execução.fetch só rejeita em falhas de rede (erros de DNS, sem conexão).response.ok ou response.status manualmente.Date).type ApiResult<T> =
| { success: true; data: T }
| { success: false; error: ApiError };success atua como o discriminador.data e error com base na verificação de success..data em um estado null causa um erro em tempo de execução.unknown força você a refinar ou validar antes de acessar propriedades.any permite silenciosamente todo o acesso a propriedades sem verificações.const data: unknown = await response.json() como ponto de partida para validação.Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥