@sadinho/http-client
v1.0.0
Published
A lightweight, feature-rich HTTP client with interceptors, retry logic, and timeout handling
Maintainers
Readme
HTTP Client
Substituto direto do Axios com suporte TypeScript, interceptadores, retry automático e timeout — sem dependência do axios.
Pronto para migração de equipes que querem sair do Axios.
Um cliente HTTP moderno para equipes que usam React e Node.js e querem substituir o Axios por uma alternativa menor, sem dependências de produção e baseada em Fetch.
Nota de segurança: este projeto foi criado em resposta às recentes falhas e preocupações de segurança reportadas no Axios. O objetivo é oferecer uma alternativa moderna, menor e sem dependências de produção para equipes que querem sair do axios em ambientes críticos.
Português Brasileiro | English
Por que não Axios?
O Axios é uma biblioteca sólida, mas muitas equipes hoje querem um cliente menor, sem dependências de produção, com runtime mais simples e uma postura de segurança mais clara. O HTTP Client foi criado para esse cenário de migração.
- Substitua o Axios com mudanças mínimas no código
- Remova uma dependência de produção da stack
- Use uma camada de transporte baseada em Fetch
- Mantenha interceptadores, timeout, retry e helpers de arquivos
- Adote um cliente criado especificamente após as recentes preocupações de segurança do Axios
Por que HTTP Client?
- 🎯 Substituir o Axios: Troca o Axios sem mudar seus padrões de código
- 🚀 Leve: ~5KB minificado, zero dependências de produção
- 📝 Totalmente Tipado: Suporte completo a TypeScript
- 🔄 Interceptadores: Interceptadores de request e response (mesmo padrão do Axios)
- ⏱️ Timeout: Suporte nativo de timeout
- 🔁 Retry Automático: Sistema inteligente de retry com backoff exponencial
- 📦 Transferência de Arquivos: Utilitários para download e manipulação de arquivos
- 🎯 Flexível: Suporte para todos os métodos HTTP e tipos de response
- ⚡ Rápido: Construído na moderna Fetch API
Comparativo com Axios
| Capacidade | HTTP Client | Axios | | --- | --- | --- | | Substituição direta da API | Sim | Sim | | Dependências em produção | Zero | Sim | | Tamanho do bundle | Menor | Maior | | Suporte a TypeScript | Nativo | Nativo | | Interceptadores | Sim | Sim | | Retry | Nativo | Exige configuração extra | | Timeout | Nativo | Sim | | Helpers para arquivos | Sim | Limitado | | Camada de transporte | Fetch API | Camada de adapter própria | | Postura de segurança | Projetado como alternativa enxuta | Depende do ciclo de vida do pacote upstream |
Migrando do Axios
A API foi desenhada para ser familiar, então grande parte do código Axios pode ser movida com poucas alterações.
| Axios | HTTP Client |
| --- | --- |
| axios.create({ baseURL }) | new HttpClient({ baseURL }) |
| axios.get('/usuarios') | api.get('/usuarios') |
| axios.post('/usuarios', data) | api.post('/usuarios', data) |
| axios.interceptors.request.use(...) | api.interceptors.request.use(...) |
| axios.interceptors.response.use(...) | api.interceptors.response.use(...) |
// Axios
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com'
});
const { data } = await api.get('/usuarios');// HTTP Client
import { HttpClient } from '@http-client/core';
const api = new HttpClient({
baseURL: 'https://api.example.com'
});
const { data } = await api.get('/usuarios');Características
- ✨ Mesma interface do Axios — sem curva de aprendizado
- 🔒 Type-safe com TypeScript
- 📦 Funciona com React, Vue, Next.js e Node.js
- 🎨 Fácil de customizar e estender
- 🧪 Totalmente testado
Instalação
npm install @http-client/coreou
yarn add @http-client/coreou
pnpm add @http-client/coreInício Rápido
Uso Básico (estilo Axios)
import { HttpClient } from '@http-client/core';
// Criar uma instância
const api = new HttpClient({
baseURL: 'https://api.example.com'
});
// GET
const { data } = await api.get('/usuarios');
// GET com parâmetros
const response = await api.get('/buscar', {
params: { q: 'typescript', limite: 10 }
});
// POST
const novoUsuario = await api.post('/usuarios', {
nome: 'João Silva',
email: '[email protected]'
});
// PUT/PATCH/DELETE
await api.put('/usuarios/1', { nome: 'Jane Doe' });
await api.patch('/usuarios/1', { status: 'ativo' });
await api.delete('/usuarios/1');Em Projetos React
// hooks/useApi.ts
import { useEffect, useState } from 'react';
import { HttpClient } from '@http-client/core';
const api = new HttpClient({
baseURL: process.env.REACT_APP_API_URL
});
export function useApi<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
api.get<T>(url)
.then(res => setData(res.data))
.catch(err => setError(err))
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}
// App.tsx
import { useApi } from './hooks/useApi';
interface Usuario {
id: number;
nome: string;
email: string;
}
export function ListaUsuarios() {
const { data: usuarios, loading } = useApi<Usuario[]>('/usuarios');
if (loading) return <div>Carregando...</div>;
return (
<ul>
{usuarios?.map(usuario => (
<li key={usuario.id}>{usuario.nome} - {usuario.email}</li>
))}
</ul>
);
}Interceptadores (compatível com Axios)
import { HttpClient, HttpClientError } from '@http-client/core';
const api = new HttpClient({
baseURL: 'https://api.example.com'
});
// Interceptador de request - adicionar token de autenticação
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
return config;
},
(error) => Promise.reject(error)
);
// Interceptador de response - tratar erros
api.interceptors.response.use(
(response) => {
console.log('Response:', response.status);
return response;
},
(error) => {
if (error instanceof HttpClientError) {
if (error.response.status === 401) {
// Lidar com não autorizado
localStorage.removeItem('authToken');
window.location.href = '/login';
}
}
return Promise.reject(error);
}
);Configuração Avançada
import { HttpClient } from '@http-client/core';
const api = new HttpClient({
baseURL: 'https://api.example.com',
timeout: 10000, // 10 segundos
retry: 3, // Tentar 3 vezes
retryDelay: (attempt) => {
// Backoff exponencial: 1s, 2s, 4s
return Math.pow(2, attempt) * 1000;
},
retryOnStatuses: [408, 429, 500, 502, 503, 504],
retryOnNetworkError: true,
retryOnTimeout: true,
headers: {
'X-Custom-Header': 'value'
}
});
// Fazer requests com configuração custom
const response = await api.request({
method: 'POST',
url: '/upload',
data: formData,
responseType: 'json',
timeout: 30000
});Configuração
Opções de RequestConfig
interface RequestConfig {
url?: string; // URL do request
baseURL?: string; // URL base
params?: QueryParams; // Parâmetros de query
data?: unknown; // Corpo do request
headers?: HeadersInit; // Headers customizados
method?: HttpMethod; // GET, POST, PUT, DELETE, PATCH
timeout?: number; // Timeout em ms
retry?: number; // Número de tentativas
retryDelay?: number | ((attempt, error) => number); // Delay entre tentativas
retryOnStatuses?: number[]; // Status codes para retry
retryOnMethods?: HttpMethod[]; // Métodos para retry
retryOnNetworkError?: boolean; // Retry em erro de rede
retryOnTimeout?: boolean; // Retry em timeout
responseType?: HttpResponseType; // 'json' | 'text' | 'blob' | 'arrayBuffer'
}Tratamento de Erros
import { HttpClientError, HttpClientTimeoutError } from '@http-client/core';
try {
const response = await api.get('/dados');
} catch (error) {
if (error instanceof HttpClientError) {
console.error('Erro HTTP:', error.response.status);
console.error('Dados da resposta:', error.response.data);
} else if (error instanceof HttpClientTimeoutError) {
console.error('Timeout após:', error.config.timeout);
} else {
console.error('Erro desconhecido:', error);
}
}Transferência de Arquivos
import {
downloadBlob,
extractFilenameFromContentDisposition,
getFilenameFromResponseHeaders,
inferExtensionFromMimeType
} from '@http-client/core';
// Baixar um arquivo
const response = await api.get('/api/documento.pdf', {
responseType: 'blob'
});
const filename = getFilenameFromResponseHeaders(response.headers);
downloadBlob(response.data, filename);Comparação com Axios
| Característica | Axios | HTTP Client | |----------------|-------|------------| | Tamanho do bundle | ~13 KB | ~5 KB | | Dependências | 1 | 0 | | Compatibilidade de API | - | ✅ 100% | | Interceptadores | ✅ Sim | ✅ Sim | | Retry automático | ❌ Não | ✅ Sim | | Timeout | ✅ Sim | ✅ Sim | | TypeScript | ✅ Sim | ✅ Completo | | Tipos de response | ✅ Sim | ✅ Sim | | Cancel tokens | ✅ Sim | ❌ Não* |
*Use AbortController em vez disso (nativa do fetch)
Migrando do Axios
Simplesmente substitua o import:
// Antes
import axios from 'axios';
const api = axios.create({ baseURL: 'https://api.example.com' });
// Depois
import { HttpClient } from '@http-client/core';
const api = new HttpClient({ baseURL: 'https://api.example.com' });
// Todo o resto permanece igual! ✨
const { data } = await api.get('/usuarios');Publicando no NPM
Opção 1: Publicar via CLI
Configure o package.json:
{ "name": "@sua-org/http-client", "version": "1.0.0", "author": "Seu Nome", "repository": { "type": "git", "url": "https://github.com/sua-org/http-client" } }Criar conta npm:
npm adduserBuild e testes:
npm run build npm testPublicar:
npm publish
Opção 2: Publicação Automática com GitHub Actions
O repositório inclui workflow do GitHub Actions para publicação automática.
Gerar token npm:
- Visite: https://www.npmjs.com/settings/~profile/tokens
- Crie um token "Publish"
- Copie o token
Adicionar secret no GitHub:
- Acesse Settings → Secrets and variables → Actions
- Crie um secret chamado
NPM_TOKEN - Cole o token do npm
Atualizar package.json:
{ "name": "@sua-org/http-client" }Push para a branch main:
git push origin main→ Testes automáticos e publicação! 🚀
Veja PUBLISHING.md para instruções detalhadas.
Desenvolvimento
Instalar dependências
npm installBuild
npm run buildModo watch
npm run build:watchStorybook (documentação interativa)
npm run storybook
# Abra http://localhost:6006Testes
npm testTestes com interface visual
npm run test:uiLint
npm run lintContribuindo
Contribuições são bem-vindas! Veja CONTRIBUTING.md para diretrizes.
Exemplos do Mundo Real
Serviço de API com Autenticação
import { HttpClient, HttpClientError } from '@http-client/core';
class ServicoAPI {
private api: HttpClient;
constructor(baseURL: string) {
this.api = new HttpClient({ baseURL });
this.configurarInterceptadores();
}
private configurarInterceptadores() {
// Adicionar token de autenticação
this.api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
return config;
});
// Tratar erros globalmente
this.api.interceptors.response.use(
(response) => response,
(error) => {
if (error instanceof HttpClientError) {
if (error.response.status === 401) {
this.tratarNaoAutorizado();
}
}
throw error;
}
);
}
private tratarNaoAutorizado() {
localStorage.removeItem('token');
window.location.href = '/login';
}
async obterUsuarios() {
return this.api.get('/usuarios');
}
async criarUsuario(dados: any) {
return this.api.post('/usuarios', dados);
}
}
export const servicoAPI = new ServicoAPI(
process.env.REACT_APP_API_URL || 'https://api.example.com'
);Hook React para Busca de Dados
import { useEffect, useState } from 'react';
import { HttpClientError } from '@http-client/core';
import { servicoAPI } from './services/api';
export function useDados<T>(endpoint: string) {
const [dados, setDados] = useState<T | null>(null);
const [carregando, setCarregando] = useState(true);
const [erro, setErro] = useState<string | null>(null);
useEffect(() => {
let montado = true;
servicoAPI.api.get<T>(endpoint)
.then(response => {
if (montado) {
setDados(response.data);
setErro(null);
}
})
.catch(err => {
if (montado) {
const mensagem = err instanceof HttpClientError
? `Erro ${err.response.status}`
: 'Erro de rede';
setErro(mensagem);
setDados(null);
}
})
.finally(() => {
if (montado) setCarregando(false);
});
return () => {
montado = false;
};
}, [endpoint]);
return { dados, carregando, erro };
}Licença
MIT
