npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sadinho/http-client

v1.0.0

Published

A lightweight, feature-rich HTTP client with interceptors, retry logic, and timeout handling

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/core

ou

yarn add @http-client/core

ou

pnpm add @http-client/core

Iní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

  1. 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" }
    }
  2. Criar conta npm:

    npm adduser
  3. Build e testes:

    npm run build
    npm test
  4. Publicar:

    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.

  1. Gerar token npm:

    • Visite: https://www.npmjs.com/settings/~profile/tokens
    • Crie um token "Publish"
    • Copie o token
  2. Adicionar secret no GitHub:

    • Acesse Settings → Secrets and variables → Actions
    • Crie um secret chamado NPM_TOKEN
    • Cole o token do npm
  3. Atualizar package.json:

    {
      "name": "@sua-org/http-client"
    }
  4. 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 install

Build

npm run build

Modo watch

npm run build:watch

Storybook (documentação interativa)

npm run storybook
# Abra http://localhost:6006

Testes

npm test

Testes com interface visual

npm run test:ui

Lint

npm run lint

Contribuindo

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