Cliente HTTP em TypeScript: fazendo requisições corretamente
TypeScript é uma extensão fortemente tipada do JavaScript que adiciona confiabilidade e legibilidade ao código no desenvolvimento web. Ao trabalhar com APIs externas ou backend, a habilidade de executar requisições HTTP corretamente torna-se essencial. Neste artigo, vamos abordar como criar um cliente HTTP completo em TypeScript, desde os princípios básicos até padrões avançados de tratamento de erros e tipagem.
Por que TypeScript é ideal para clientes HTTP?
JavaScript oferece várias formas de executar requisições HTTP: o nativo fetch, as bibliotecas axios e node-fetch. No entanto, o TypeScript permite elevar o trabalho com elas a um novo nível graças à tipagem estática. Você pode descrever antecipadamente a estrutura da requisição e da resposta, eliminando muitos erros em tempo de compilação. Imagine que você está trabalhando com uma API que retorna uma lista de usuários. Sem tipos, você corre o risco de acessar um campo inexistente, e o erro só aparecerá em tempo de execução. O TypeScript resolve esse problema.
Parte principal: criando um cliente HTTP do zero
1. Requisição GET básica com fetch e tipagem
Vamos começar com o mais simples — uma requisição GET. No TypeScript, podemos definir uma interface para a resposta e passá-la para a função genérica fetch. Veja o exemplo:
interface User { id: number; name: string; email: string;}
async function getUser(id: number): Promise<User> { const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`); if (!response.ok) { throw new Error(`Erro HTTP! status: ${response.status}`); } const data: User = await response.json(); return data;}
// UsogetUser(1) .then(user => console.log(user.name)) .catch(error => console.error('Erro:', error.message));Aqui, indicamos explicitamente que a função retorna Promise<User>. Se a API mudar, o compilador TypeScript avisará sobre a incompatibilidade de tipos. Observe a verificação de response.ok — este é um passo obrigatório para tratar erros HTTP (404, 500, etc.).
2. Requisição POST com corpo e cabeçalhos
Enviar dados para o servidor requer configuração de cabeçalhos e serialização do corpo da requisição. Vamos criar uma função para adicionar um novo usuário:
interface CreateUserRequest { name: string; email: string;}
interface CreateUserResponse { id: number; name: string; email: string;}
async function createUser(userData: CreateUserRequest): Promise<CreateUserResponse> { const response = await fetch('https://jsonplaceholder.typicode.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(userData), });
if (!response.ok) { const errorBody = await response.text(); throw new Error(`Erro ao criar: ${response.status} - ${errorBody}`); }
return await response.json();}
// Exemplo de chamadaconst newUser: CreateUserRequest = { name: 'João', email: 'joao@example.com' };createUser(newUser).then(result => console.log('ID criado:', result.id));A tipagem de CreateUserRequest e CreateUserResponse torna o código autodocumentado. Outros desenvolvedores veem imediatamente quais campos são obrigatórios para envio e o que esperar na resposta.
3. Cliente HTTP universal com tratamento de erros
Em projetos reais, raramente se usa apenas uma função. É melhor criar uma classe ou conjunto de funções universais que centralizem a lógica das requisições. Veja um exemplo de cliente avançado:
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
interface RequestConfig { method: HttpMethod; endpoint: string; body?: unknown; params?: Record<string, string>;}
class HttpClient { private baseUrl: string;\