Cliente HTTP em TypeScript: requisições corretas e tratamento de respostas

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: por que um cliente HTTP correto é a base de uma aplicação web moderna

Qualquer aplicação web moderna interage com o servidor via HTTP. Seja carregando uma lista de produtos, enviando um formulário de registro ou obtendo dados para um dashboard — tudo se resume ao envio de requisições HTTP. Com TypeScript, com seu poderoso sistema de tipos, é possível construir não apenas um cliente funcional, mas um cliente HTTP verdadeiramente confiável e previsível.

Neste artigo, vamos analisar como criar um cliente HTTP em TypeScript que tratará erros, tipará respostas corretamente, gerenciará cabeçalhos e suportará middleware. Você aprenderá por que usar fetch puro sem um wrapper é uma má prática e como transformá-lo em uma ferramenta poderosa.



Fundamentos: do fetch puro ao cliente tipado

Vamos começar com um exemplo básico. O fetch embutido no JavaScript é um bom ponto de partida, mas ele não fornece tipagem conveniente nem tratamento de erros. Com TypeScript, podemos encapsulá-lo em nossa própria função.

Wrapper simples para fetch

Vamos criar uma classe base HttpClient que aceitará uma URL base e configurações.

interface RequestOptions {  headers?: Record<string, string>;  params?: Record<string, string>;}

class HttpClient { private baseUrl: string; private defaultHeaders: Record<string, string>;

constructor(baseUrl: string, defaultHeaders: Record<string, string> = {}) { this.baseUrl = baseUrl; this.defaultHeaders = defaultHeaders; }

private buildUrl(path: string, params?: Record<string, string>): string { const url = new URL(`${this.baseUrl}${path}`); if (params) { Object.entries(params).forEach(([key, value]) => url.searchParams.append(key, value)); } return url.toString(); }

async get<T>(path: string, options?: RequestOptions): Promise<T> { const url = this.buildUrl(path, options?.params); const response = await fetch(url, { method: 'GET', headers: { ...this.defaultHeaders, ...options?.headers }, });

if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); }

return response.json() as Promise<T>; }}

Agora, a chamada client.get<User>('/users/1') retornará um objeto User tipado. Isso já é muito melhor do que trabalhar com any.



Tratamento avançado de erros e tipagem de respostas

Simplesmente lançar um erro não é a melhor abordagem. Precisamos distinguir entre erros de rede, erros do servidor (4xx, 5xx) e erros de parsing de JSON. Vamos criar nossas próprias classes de erro.

Criação de classes de erro

class HttpError extends Error {  status: number;  body: any;

constructor(message: string, status: number, body?: any) { super(message); this.name = 'HttpError'; this.status = status; this.body = body; }}

class NetworkError extends Error { constructor(message: string) { super(message); this.name = 'NetworkError'; }}

class ParseError extends Error { constructor(message: string) { super(message); this.name = 'ParseError'; }}

Tratamento aprimorado no método get

async get<T>(path: string, options?: RequestOptions): Promise<T> {  try {    const url = this.buildUrl(path, options?.params);    const response = await fetch(url, {      method: 'GET',      headers: { ...this.defaultHeaders, ...options?.headers },    });

let body: any; try { body = await response.json(); } catch { throw new ParseError('Failed to parse response as JSON'); }

if (!response.ok) { throw new HttpError( `Request failed with status ${response.status}`, response.status, body ); }

return body as T; } catch (er

Blogs

Book Recommendations