Cómo Tipar Respuestas de API en TypeScript
Todo desarrollador frontend ha pasado por esto: obtienes datos de una API, accedes a una propiedad y obtienes undefined en tiempo de ejecución, aunque TypeScript nunca se quejó. El problema no es tu código. Es que el sistema de tipos de TypeScript solo opera en tiempo de compilación. No tiene idea de lo que tu API realmente devuelve.
Este artículo cubre patrones prácticos para tipar respuestas de API en TypeScript, desde la definición de interfaces básicas hasta la validación en tiempo de ejecución y la generación de tipos basada en contratos.
Puntos Clave
- El sistema de tipos de TypeScript opera solo en tiempo de compilación: no puede validar la forma real de los datos devueltos por una API en tiempo de ejecución.
- Usa interfaces o alias de tipos con uniones discriminadas para definir formas de respuesta claras y esperadas.
- Un wrapper genérico de fetch mantiene los sitios de llamada limpios y hace explícitos los tipos esperados.
- Para datos no confiables o externos, usa bibliotecas de validación en tiempo de ejecución como Zod, Valibot o ArkType para cerrar la brecha entre los tipos en tiempo de compilación y las respuestas del mundo real.
- Cuando existe una especificación OpenAPI, genera tus tipos a partir de ella para mantener sincronizados los contratos de frontend y backend.
Por Qué TypeScript No Puede Protegerte en Tiempo de Ejecución
Cuando llamas a response.json(), TypeScript típicamente trata el resultado como unknown (o a veces any, dependiendo de los tipados del entorno). Eso significa que puedes castearlo a lo que quieras, y el compilador confiará en ti completamente.
// ❌ TypeScript confía ciegamente en este cast
const data = (await response.json()) as User
console.log(data.email) // Podría ser undefined en tiempo de ejecución
Este es el límite fundamental que debes entender: el tipado de respuestas de API en TypeScript te da seguridad en tiempo de compilación, pero no valida el JSON real que tu API devuelve. Si la forma cambia, TypeScript no lo sabrá.
Definiendo Tipos para Formas de Respuesta Esperadas
El primer paso es dar estructura a lo que esperas. Usa interfaces o alias de tipos para describir la forma de la respuesta:
interface User {
id: string
name: string
email: string
}
Para APIs consistentes, un wrapper genérico maneja tanto estados de éxito como de error de forma limpia:
type ApiResponse<T> =
| { status: "ok"; data: T }
| { status: "error"; message: string }
Este patrón de unión discriminada es más transparente que un solo tipo con muchos campos opcionales. Cuando verificas status, TypeScript reduce el tipo automáticamente: no se necesitan guardias adicionales.
Tipando Respuestas de Fetch con un Wrapper Genérico
Tipar respuestas de fetch en TypeScript es más limpio con un helper reutilizable:
async function apiFetch<T>(url: string): Promise<T> {
const response = await fetch(url)
if (!response.ok) throw new Error(`HTTP error: ${response.status}`)
return response.json() as Promise<T>
}
// Uso
const user = await apiFetch<User>("/api/users/1")
Esto mantiene tus sitios de llamada limpios mientras hace explícita la forma esperada. El mismo patrón funciona con Axios o cualquier otro cliente HTTP.
Discover how at OpenReplay.com.
Validación en Tiempo de Ejecución: Cerrando la Brecha
Las aserciones de tipo como as User son una promesa al compilador, no una garantía. Para datos externos no confiables (APIs de terceros, contenido generado por usuarios o cualquier respuesta que no controles completamente) necesitas validación en tiempo de ejecución.
Zod es la opción más utilizada. Defines un esquema, analizas la respuesta y obtienes un resultado completamente tipado:
import { z } from "zod"
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
})
type User = z.infer<typeof UserSchema> // Derivado del esquema
const data = await response.json()
const user = UserSchema.parse(data) // Lanza error si la forma es incorrecta
El beneficio clave: tus tipos de TypeScript y tu validación en tiempo de ejecución permanecen sincronizados porque derivan de la misma fuente de verdad.
Alternativas que vale la pena conocer:
- Valibot — bundle más pequeño, API similar a Zod
- ArkType — altamente eficiente, sintaxis nativa de TypeScript
Usa .parse() cuando quieras lanzar un error con datos inválidos, o .safeParse() cuando quieras manejar errores de forma elegante sin excepciones.
Tipado Basado en Contratos con OpenAPI
Si tu API tiene una especificación OpenAPI, puedes generar tipos de TypeScript automáticamente usando openapi-typescript:
npx openapi-typescript ./api-spec.yaml -o ./types/api.d.ts
Este enfoque mantiene tus tipos sincronizados con la documentación de tu API. Es complementario a la validación en tiempo de ejecución: los tipos generados manejan la capa de tiempo de compilación, mientras que Zod o Valibot manejan la capa de tiempo de ejecución.
Eligiendo el Enfoque Correcto
| Escenario | Patrón Recomendado |
|---|---|
| API interna que controlas | Interface + aserción de tipo |
| Contrato compartido con backend | Tipos generados con OpenAPI |
| API de terceros o no confiable | Validación de esquema con Zod/Valibot |
| Se necesitan ambas capas de seguridad | Tipos generados + esquema en tiempo de ejecución |
Conclusión
TypeScript te da estructura y autocompletado, pero no puede validar lo que llega a través de la red. Para APIs en las que confías, una interfaz bien definida y un wrapper de fetch tipado son suficientes. Para datos externos o impredecibles, combina tus tipos con un validador en tiempo de ejecución como Zod. Cuando tienes una especificación de API, genera tus tipos a partir de ella. Estos enfoques no compiten: funcionan mejor juntos.
Preguntas Frecuentes
No. La palabra clave 'as' es una aserción de tipo, no una verificación en tiempo de ejecución. Le dice al compilador que trate un valor como un tipo específico, pero no hace nada para verificar los datos reales. Si la forma de la respuesta de la API difiere de lo que aseveraste, obtendrás errores en tiempo de ejecución que TypeScript no puede detectar.
Usa Zod o una biblioteca similar de validación en tiempo de ejecución siempre que consumas datos de una fuente que no controlas completamente, como APIs de terceros o endpoints públicos. Si eres dueño tanto del frontend como del backend y tienes una confianza sólida en la forma de la respuesta, una interfaz simple con una aserción de tipo suele ser suficiente.
Sí, y esta combinación es recomendada para máxima seguridad. Los tipos generados con OpenAPI te dan autocompletado y verificación de tipos en tiempo de compilación, mientras que los esquemas de Zod validan los datos de respuesta reales en tiempo de ejecución. Juntos, cubren ambas capas del espectro de seguridad de tipos.
El método response.json() de la API Fetch devuelve Promise<any> por defecto en TypeScript. Esto significa que el compilador no aplicará ninguna verificación de tipo en el resultado a menos que lo aseveres o valides explícitamente. Algunos equipos sobrescriben esto con tipados más estrictos que devuelven unknown en su lugar, forzando la validación explícita.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.