Español

Domine las llamadas API con seguridad de tipos en TypeScript para aplicaciones web robustas, mantenibles y sin errores. Aprenda las mejores prácticas y técnicas avanzadas.

Llamadas API Seguras con Tipos en TypeScript: Una Guía Completa

En el desarrollo web moderno, interactuar con APIs es una tarea fundamental. TypeScript, con su potente sistema de tipos, ofrece una ventaja significativa para garantizar la fiabilidad y el mantenimiento de sus aplicaciones al permitir llamadas API con seguridad de tipos. Esta guía explorará cómo aprovechar las funciones de TypeScript para construir interacciones API robustas y sin errores, cubriendo las mejores prácticas, técnicas avanzadas y ejemplos del mundo real.

Por qué la Seguridad de Tipos es Importante para las Llamadas API

Cuando se trabaja con APIs, básicamente se trata con datos provenientes de una fuente externa. Es posible que estos datos no siempre estén en el formato que espera, lo que lleva a errores en tiempo de ejecución y un comportamiento inesperado. La seguridad de tipos proporciona una capa crucial de protección al verificar que los datos que recibe se ajustan a una estructura predefinida, detectando problemas potenciales al principio del proceso de desarrollo.

Configuración de su proyecto TypeScript

Antes de sumergirse en las llamadas API, asegúrese de tener un proyecto TypeScript configurado. Si está comenzando desde cero, puede inicializar un nuevo proyecto usando:

npm init -y
npm install typescript --save-dev
tsc --init

Esto creará un archivo `tsconfig.json` con las opciones predeterminadas del compilador TypeScript. Puede personalizar estas opciones para que se adapten a las necesidades de su proyecto. Por ejemplo, es posible que desee habilitar el modo estricto para una comprobación de tipos más estricta:

// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Definición de tipos para las respuestas de la API

El primer paso para lograr llamadas API con seguridad de tipos es definir los tipos de TypeScript que representan la estructura de los datos que espera recibir de la API. Esto generalmente se hace usando declaraciones `interface` o `type`.

Usando Interfaces

Las interfaces son una forma poderosa de definir la forma de un objeto. Por ejemplo, si está recuperando una lista de usuarios de una API, podría definir una interfaz como esta:

interface User {
  id: number;
  name: string;
  email: string;
  address?: string; // Propiedad opcional
  phone?: string; // Propiedad opcional
  website?: string; // Propiedad opcional
  company?: {
    name: string;
    catchPhrase: string;
    bs: string;
  };
}

El `?` después del nombre de una propiedad indica que la propiedad es opcional. Esto es útil para manejar respuestas de API donde faltan ciertos campos.

Usando Tipos

Los tipos son similares a las interfaces, pero ofrecen más flexibilidad, incluida la capacidad de definir tipos de unión e tipos de intersección. Puede lograr el mismo resultado que la interfaz anterior usando un tipo:

type User = {
  id: number;
  name: string;
  email: string;
  address?: string; // Propiedad opcional
  phone?: string; // Propiedad opcional
  website?: string; // Propiedad opcional
  company?: {
    name: string;
    catchPhrase: string;
    bs: string;
  };
};

Para estructuras de objetos simples, las interfaces y los tipos a menudo son intercambiables. Sin embargo, los tipos se vuelven más poderosos cuando se trata de escenarios más complejos.

Realización de llamadas API con Axios

Axios es un cliente HTTP popular para realizar solicitudes API en JavaScript y TypeScript. Proporciona una API limpia e intuitiva, lo que facilita el manejo de diferentes métodos HTTP, encabezados de solicitud y datos de respuesta.

Instalación de Axios

npm install axios

Realización de una llamada API con tipos

Para realizar una llamada API con seguridad de tipos con Axios, puede usar el método `axios.get` y especificar el tipo de respuesta esperado usando genéricos:

import axios from 'axios';

async function fetchUsers(): Promise<User[]> {
  try {
    const response = await axios.get<User[]>('https://jsonplaceholder.typicode.com/users');
    return response.data;
  } catch (error) {
    console.error('Error al obtener usuarios:', error);
    throw error;
  }
}

fetchUsers().then(users => {
  users.forEach(user => {
    console.log(user.name);
  });
});

En este ejemplo, `axios.get<User[]>('...')` le dice a TypeScript que se espera que los datos de la respuesta sean una matriz de objetos `User`. Esto permite que TypeScript proporcione la comprobación de tipos y la autocompletado al trabajar con los datos de la respuesta.

Manejo de diferentes métodos HTTP

Axios admite varios métodos HTTP, incluidos `GET`, `POST`, `PUT`, `DELETE` y `PATCH`. Puede usar los métodos correspondientes para realizar diferentes tipos de solicitudes API. Por ejemplo, para crear un nuevo usuario, puede usar el método `axios.post`:

async function createUser(user: Omit<User, 'id'>): Promise<User> {
  try {
    const response = await axios.post<User>('https://jsonplaceholder.typicode.com/users', user);
    return response.data;
  } catch (error) {
    console.error('Error al crear usuario:', error);
    throw error;
  }
}

const newUser = {
  name: 'John Doe',
  email: 'john.doe@example.com',
  address: '123 Main St',
  phone: '555-1234',
  website: 'example.com',
  company: {
    name: 'Example Corp',
    catchPhrase: 'Leading the way',
    bs: 'Innovative solutions'
  }
};

createUser(newUser).then(user => {
  console.log('Usuario creado:', user);
});

En este ejemplo, `Omit<User, 'id'>` crea un tipo que es el mismo que `User` pero sin la propiedad `id`. Esto es útil porque el `id` generalmente es generado por el servidor al crear un nuevo usuario.

Usando la API Fetch

La API Fetch es una API JavaScript incorporada para realizar solicitudes HTTP. Si bien es más básica que Axios, también se puede usar con TypeScript para lograr llamadas API con seguridad de tipos. Puede preferirlo para evitar agregar una dependencia si se adapta a sus necesidades.

Realización de una llamada API con tipos con Fetch

Para realizar una llamada API con seguridad de tipos con Fetch, puede usar la función `fetch` y luego analizar la respuesta como JSON, especificando el tipo de respuesta esperado:

async function fetchUsers(): Promise<User[]> {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    if (!response.ok) {
      throw new Error(`¡Error HTTP! estado: ${response.status}`);
    }
    const data: User[] = await response.json();
    return data;
  } catch (error) {
    console.error('Error al obtener usuarios:', error);
    throw error;
  }
}

fetchUsers().then(users => {
  users.forEach(user => {
    console.log(user.name);
  });
});

En este ejemplo, `const data: User[] = await response.json();` le dice a TypeScript que los datos de la respuesta deben tratarse como una matriz de objetos `User`. Esto permite que TypeScript realice la comprobación de tipos y la autocompletado.

Manejo de diferentes métodos HTTP con Fetch

Para realizar diferentes tipos de solicitudes API con Fetch, puede usar la función `fetch` con diferentes opciones, como las opciones `method` y `body`. Por ejemplo, para crear un nuevo usuario, puede usar el siguiente código:

async function createUser(user: Omit<User, 'id'>): Promise<User> {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(user)
    });
    if (!response.ok) {
      throw new Error(`¡Error HTTP! estado: ${response.status}`);
    }
    const data: User = await response.json();
    return data;
  } catch (error) {
    console.error('Error al crear usuario:', error);
    throw error;
  }
}

const newUser = {
  name: 'John Doe',
  email: 'john.doe@example.com',
  address: '123 Main St',
  phone: '555-1234',
  website: 'example.com',
  company: {
    name: 'Example Corp',
    catchPhrase: 'Leading the way',
    bs: 'Innovative solutions'
  }
};

createUser(newUser).then(user => {
  console.log('Usuario creado:', user);
});

Manejo de errores de API

El manejo de errores es un aspecto crítico de las llamadas API. Las API pueden fallar por muchas razones, incluidos problemas de conectividad de red, errores del servidor y solicitudes no válidas. Es esencial manejar estos errores con elegancia para evitar que su aplicación se bloquee o muestre un comportamiento inesperado.

Uso de bloques Try-Catch

La forma más común de manejar los errores en el código asíncrono es usar bloques try-catch. Esto le permite detectar cualquier excepción que se genere durante la llamada API y manejarlas de manera adecuada.

async function fetchUsers(): Promise<User[]> {
  try {
    const response = await axios.get<User[]>('https://jsonplaceholder.typicode.com/users');
    return response.data;
  } catch (error) {
    console.error('Error al obtener usuarios:', error);
    // Manejar el error, por ejemplo, mostrar un mensaje de error al usuario
    throw error; // Volver a generar el error para permitir que el código que llama también lo maneje
  }
}

Manejo de códigos de error específicos

Las API a menudo devuelven códigos de error específicos para indicar el tipo de error que ocurrió. Puede usar estos códigos de error para proporcionar un manejo de errores más específico. Por ejemplo, es posible que desee mostrar un mensaje de error diferente para un error 404 No encontrado que para un error 500 Error interno del servidor.

async function fetchUser(id: number): Promise<User | null> {
  try {
    const response = await axios.get<User>(`https://jsonplaceholder.typicode.com/users/${id}`);
    return response.data;
  } catch (error: any) {
    if (error.response?.status === 404) {
      console.log(`Usuario con ID ${id} no encontrado.`);
      return null; // O lanzar un error personalizado
    } else {
      console.error('Error al obtener usuario:', error);
      throw error;
    }
  }
}

fetchUser(123).then(user => {
  if (user) {
    console.log('Usuario:', user);
  } else {
    console.log('Usuario no encontrado.');
  }
});

Creación de tipos de error personalizados

Para escenarios de manejo de errores más complejos, puede crear tipos de error personalizados para representar diferentes tipos de errores de API. Esto le permite proporcionar información de error más estructurada y manejar los errores de manera más efectiva.

class ApiError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message);
    this.name = 'ApiError';
  }
}

async function fetchUser(id: number): Promise<User> {
  try {
    const response = await axios.get<User>(`https://jsonplaceholder.typicode.com/users/${id}`);
    return response.data;
  } catch (error: any) {
    if (error.response?.status === 404) {
      throw new ApiError(404, `Usuario con ID ${id} no encontrado.`);
    } else {
      console.error('Error al obtener usuario:', error);
      throw new ApiError(500, 'Internal Server Error'); // O cualquier otro código de estado adecuado
    }
  }
}

fetchUser(123).catch(error => {
  if (error instanceof ApiError) {
    console.error(`Error de API: ${error.statusCode} - ${error.message}`);
  } else {
    console.error('Ocurrió un error inesperado:', error);
  }
});

Validación de datos

Incluso con el sistema de tipos de TypeScript, es crucial validar los datos que recibe de las API en tiempo de ejecución. Las API pueden cambiar su estructura de respuesta sin previo aviso, y es posible que sus tipos de TypeScript no siempre estén perfectamente sincronizados con la respuesta real de la API.

Usando Zod para la validación en tiempo de ejecución

Zod es una biblioteca de TypeScript popular para la validación de datos en tiempo de ejecución. Le permite definir esquemas que describen la estructura esperada de sus datos y luego validar los datos contra esos esquemas en tiempo de ejecución.

Instalación de Zod

npm install zod

Validación de respuestas API con Zod

Para validar las respuestas de la API con Zod, puede definir un esquema Zod que corresponda a su tipo de TypeScript y luego usar el método `parse` para validar los datos.

import { z } from 'zod';

const userSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  address: z.string().optional(),
  phone: z.string().optional(),
  website: z.string().optional(),
  company: z.object({
    name: z.string(),
    catchPhrase: z.string(),
    bs: z.string(),
  }).optional(),
});

type User = z.infer<typeof userSchema>;

async function fetchUsers(): Promise<User[]> {
  try {
    const response = await axios.get<unknown>('https://jsonplaceholder.typicode.com/users');
    const data = z.array(userSchema).parse(response.data);
    return data;
  } catch (error) {
    console.error('Error al obtener usuarios:', error);
    throw error;
  }
}

En este ejemplo, `z.array(userSchema).parse(response.data)` valida que los datos de la respuesta son una matriz de objetos que se ajustan al `userSchema`. Si los datos no se ajustan al esquema, Zod generará un error, que luego podrá manejar de manera adecuada.

Técnicas avanzadas

Uso de genéricos para funciones API reutilizables

Los genéricos le permiten escribir funciones API reutilizables que pueden manejar diferentes tipos de datos. Por ejemplo, puede crear una función genérica `fetchData` que puede obtener datos de cualquier punto final de la API y devolverlos con el tipo correcto.

async function fetchData<T>(url: string): Promise<T> {
  try {
    const response = await axios.get<T>(url);
    return response.data;
  } catch (error) {
    console.error(`Error al obtener datos de ${url}:`, error);
    throw error;
  }
}

// Uso
fetchData<User[]>('https://jsonplaceholder.typicode.com/users').then(users => {
  console.log('Usuarios:', users);
});

fetchData<{ title: string; body: string }>('https://jsonplaceholder.typicode.com/todos/1').then(todo => {
    console.log('Tarea', todo)
});

Uso de interceptores para el manejo global de errores

Axios proporciona interceptores que le permiten interceptar solicitudes y respuestas antes de que su código las maneje. Puede usar interceptores para implementar el manejo global de errores, como registrar errores o mostrar mensajes de error al usuario.

axios.interceptors.response.use(
  (response) => response,
  (error) => {
    console.error('Manejador de errores global:', error);
    // Mostrar un mensaje de error al usuario
    return Promise.reject(error);
  }
);

Uso de variables de entorno para las URL de la API

Para evitar codificar las URL de la API en su código, puede usar variables de entorno para almacenar las URL. Esto facilita la configuración de su aplicación para diferentes entornos, como desarrollo, pruebas y producción.

Ejemplo usando el archivo `.env` y el paquete `dotenv`.

// .env
API_URL=https://api.example.com
// Instalar dotenv
npm install dotenv
// Importar y configurar dotenv
import * as dotenv from 'dotenv'
dotenv.config()

const apiUrl = process.env.API_URL || 'http://localhost:3000'; // proporcionar un valor predeterminado

async function fetchData<T>(endpoint: string): Promise<T> {
  try {
    const response = await axios.get<T>(`${apiUrl}/${endpoint}`);
    return response.data;
  } catch (error) {
    console.error(`Error al obtener datos de ${apiUrl}/${endpoint}:`, error);
    throw error;
  }
}

Conclusión

Las llamadas API con seguridad de tipos son esenciales para construir aplicaciones web robustas, mantenibles y sin errores. TypeScript proporciona funciones potentes que le permiten definir tipos para las respuestas de la API, validar datos en tiempo de ejecución y manejar errores con elegancia. Al seguir las mejores prácticas y técnicas descritas en esta guía, puede mejorar significativamente la calidad y la confiabilidad de sus interacciones con la API.

Al usar TypeScript y bibliotecas como Axios y Zod, puede asegurarse de que sus llamadas API sean seguras para los tipos, sus datos estén validados y sus errores se manejen con elegancia. Esto conducirá a aplicaciones más robustas y mantenibles.

Recuerde validar siempre sus datos en tiempo de ejecución, incluso con el sistema de tipos de TypeScript. Las API pueden cambiar y es posible que sus tipos no siempre estén perfectamente sincronizados con la respuesta real de la API. Al validar sus datos en tiempo de ejecución, puede detectar problemas potenciales antes de que causen problemas en su aplicación.

¡Feliz codificación!