Cliente HTTP en TypeScript: haciendo solicitudes correctamente
En el desarrollo web moderno, trabajar con solicitudes HTTP es una de las tareas clave. TypeScript, como una extensión fuertemente tipada sobre JavaScript, proporciona a los desarrolladores herramientas poderosas para crear clientes HTTP confiables y predecibles. En este artículo, analizaremos cómo organizar correctamente un cliente HTTP en TypeScript para que su código sea seguro, legible y fácil de mantener.
¿Por qué TypeScript es ideal para clientes HTTP?
TypeScript agrega tipado estático, lo cual es especialmente importante al trabajar con APIs externas. Usted sabe exactamente qué datos llegarán del servidor y puede prevenir muchos errores en la etapa de compilación. Veamos un ejemplo básico:
// Malo: JavaScript sin tiposconst data = await fetch('/api/users');const users = await data.json();// No sabemos la estructura de users
// Bueno: TypeScript con tiposinterface User { id: number; name: string; email: string;}
const response = await fetch('/api/users');const users: User[] = await response.json();// Ahora users tiene una estructura claraCreación de un cliente HTTP básico en TypeScript
Comencemos creando un cliente HTTP universal que maneje todos los tipos de solicitudes. Usaremos la API nativa fetch, disponible en navegadores modernos y Node.js 18+.
Implementación básica con tipado
interface HttpClientConfig { baseURL: string; headers?: Record<string, string>; timeout?: number;}
interface ApiResponse<T> { data: T; status: number; ok: boolean;}
class HttpClient { private baseURL: string; private headers: Record<string, string>; private timeout: number;
constructor(config: HttpClientConfig) { this.baseURL = config.baseURL; this.headers = { 'Content-Type': 'application/json', ...config.headers, }; this.timeout = config.timeout || 10000; }
private async request<T>( endpoint: string, options: RequestInit = {} ): Promise<ApiResponse<T>> { const url = `${this.baseURL}${endpoint}`; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try { const response = await fetch(url, { ...options, headers: { ...this.headers, ...options.headers }, signal: controller.signal, });
clearTimeout(timeoutId);
const data: T = await response.json();
return { data, status: response.status, ok: response.ok, }; } catch (error) { clearTimeout(timeoutId); throw this.handleError(error); } }
private handleError(error: unknown): Error { if (error instanceof DOMException && error.name === 'AbortError') { return new Error('Request timeout'); } if (error instanceof TypeError) { return new Error('Network error'); } return error as Error; }
async get<T>(endpoint: string): Promise<ApiResponse<T>> { return this.request<T>(endpoint, { method: 'GET' }); }
async post<T>(endpoint: string, body: unknown): Promise<ApiResponse<T>> { return this.request<T>(endpoint, { method: 'POST', body: JSON.stringify(body), }); }
async put<T>(endpoint: string, body: unknown): Promise<ApiResponse<T>> { return this.request<T>(endpoint, { method: 'PUT', body: JSON.stringify(body), }); }
async delete<T>(endpoint: string): Promise<ApiResponse<T>> { return this.request<T>(endpoint, { method: 'DELETE' }); }}Uso del cliente
// Creamos una instancia del clienteconst api = new HttpClient({ baseURL: 'https://api.