Cliente HTTP em TypeScript: fazendo requisições corretamente
No desenvolvimento web moderno, trabalhar com requisições HTTP é uma das tarefas principais. TypeScript, como uma extensão estritamente tipada sobre JavaScript, fornece aos desenvolvedores ferramentas poderosas para criar clientes HTTP confiáveis e previsíveis. Neste artigo, vamos analisar como organizar corretamente um cliente HTTP em TypeScript para que seu código seja seguro, legível e facilmente mantível.
Por que TypeScript é ideal para clientes HTTP?
TypeScript adiciona tipagem estática, o que é especialmente importante ao trabalhar com APIs externas. Você sabe exatamente quais dados virão do servidor e pode prevenir muitos erros em tempo de compilação. Vejamos um exemplo básico:
// Ruim: JavaScript sem tiposconst data = await fetch('/api/users');const users = await data.json();// Não sabemos a estrutura de users
// Bom: TypeScript com tiposinterface User { id: number; name: string; email: string;}
const response = await fetch('/api/users');const users: User[] = await response.json();// Agora users tem uma estrutura claraCriando um cliente HTTP básico em TypeScript
Vamos começar criando um cliente HTTP universal que lidará com todos os tipos de requisições. Usaremos a API nativa fetch, disponível em navegadores modernos e Node.js 18+.
Implementação básica com tipagem
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' }); }}Usando o cliente
// Criando uma instância do clienteconst api = new HttpClient({ baseURL: 'https://api.