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

@regcheq/http-client

v2.2.1

Published

Capa HTTP centralizada para microservicios y frontends Regcheq

Readme

@regcheq/http-client

Librería interna de cliente HTTP para todos los proyectos de Regcheq (Node.js y browser). Reemplaza el uso directo de axios con un middleware centralizado: retries, timeouts, logging, correlation ID y fix de latencia en Node 22 — todo en un solo lugar, sin repetirlo en cada servicio.

Objetivos

  • Un solo import en cada servicio; sin boilerplate de configuración.
  • Fix automático de latencia en Node 22 (keep-alive + orden DNS ipv4first).
  • Comportamiento consistente en todos los servicios: mismos defaults, mismo manejo de errores.
  • Si hay que cambiar algo global (timeout, headers, retry strategy), se cambia en esta librería — no en 16 repos.

Instalación

npm i @regcheq/http-client

Uso básico

El export principal es client — un singleton que gestiona automáticamente un pool de conexiones por origin. Úsalo igual que axios:

import { client } from '@regcheq/http-client';

// GET
const data = await client.get<Empresa>(`${process.env.API_MAIN}/empresa/123`);

// POST
const created = await client.post<Empresa>(`${process.env.API_MAIN}/empresa`, { nombre: 'Regcheq' });

// PUT
const updated = await client.put<Empresa>(`${process.env.API_MAIN}/empresa/123`, { nombre: 'Regcheq SA' });

// PATCH
const patched = await client.patch<Empresa>(`${process.env.API_MAIN}/empresa/123`, { nombre: 'Regcheq SA' });

// DELETE
await client.delete(`${process.env.API_MAIN}/empresa/123`);

No hay que configurar nada. La librería crea y reutiliza automáticamente los pools de conexión por origin.

Requisitos para desarrollo

El proyecto usa Node 22. Con nvm instalado:

nvm use
npm ci

Husky ejecuta antes de cada commit:

  • pre-commit: typecheck, lint y test — no se permite commit si alguno falla.
  • commit-msg: commitlint para que los mensajes sigan Conventional Commits (feat:, fix:, chore:, etc.).

Guía rápida por framework

NestJS

Registra HttpClientModule globalmente y usa HttpClientService:

// app.module.ts
import { HttpClientModule } from '@regcheq/http-client';

@Module({
  imports: [HttpClientModule],
})
export class AppModule {}
// cualquier-service.service.ts
import { HttpClientService } from '@regcheq/http-client';

@Injectable()
export class ReportesService {
  constructor(private readonly http: HttpClientService) {}

  async obtenerReporte(id: string) {
    return this.http.get<Reporte>(`${process.env.API_INFORMES}/reportes/${id}`);
  }

  async crearReporte(data: CreateReporteDto) {
    return this.http.post<Reporte>(`${process.env.API_INFORMES}/reportes`, data);
  }
}

Cliente dedicado por servicio (para timeout o retries distintos):

import { createClient, HttpClientError } from '@regcheq/http-client';
import type { OnModuleDestroy } from '@nestjs/common';

@Injectable()
export class CargaMasivaService implements OnModuleDestroy {
  private api = createClient(process.env.API_FILES!);

  onModuleDestroy() {
    return this.api.destroy();
  }

  async subirArchivo(data: Buffer): Promise<UploadResult> {
    return this.api.post<UploadResult>('/archivo/carga-masiva/errors', data, {
      headers: { 'Content-Type': 'application/octet-stream' },
    });
  }

  async obtenerArchivo(id: string): Promise<Buffer> {
    return this.api.buffer(`/archivo/${id}`);
  }
}

Con timeout personalizado por llamada:

// Para uploads grandes donde el timeout global de 10s no alcanza
async subirExcel(file: Buffer) {
  return this.api.post<UploadResult>('/upload/excel', file, {
    headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' },
    timeout: 60_000,
  });
}

Con retries activados para un endpoint específico:

const apiConRetry = createClient(process.env.API_LISTAS!, { retries: 2 });

LoopBack

No hay módulo especial. Usar client directamente o createClient para opciones custom:

import { client, createClient, HttpClientError } from '@regcheq/http-client';

export class MiddlewareListasService {
  // Para servicios externos más lentos, cliente dedicado con timeout propio
  private api = createClient(process.env.MIDDLEWARE_LISTAS_URL!, {
    timeout: 15_000,
  });

  async consultarLista(rut: string): Promise<ListaResult> {
    return this.api.post<ListaResult>('/consulta', { rut });
  }

  async consultarConTimeout(rut: string): Promise<ListaResult> {
    return this.api.post<ListaResult>('/consulta-batch', { rut }, {
      timeout: 30_000, // sobreescribe solo esta llamada
    });
  }
}

Múltiples destinos — usar client con URL completa:

export class GatewayService {
  async procesarSolicitud(id: string) {
    const [empresa, archivos] = await Promise.all([
      client.get<Empresa>(`${process.env.API_MAIN}/empresa/${id}`),
      client.get<Archivo[]>(`${process.env.API_FILES}/archivos?empresaId=${id}`),
    ]);
    return { empresa, archivos };
  }
}

Express

import express from 'express';
import { client, HttpClientError } from '@regcheq/http-client';

const app = express();

app.get('/empresa/:id', async (req, res, next) => {
  try {
    const empresa = await client.get<Empresa>(`${process.env.API_MAIN}/empresa/${req.params.id}`);
    res.json(empresa);
  } catch (err) {
    next(err);
  }
});

app.post('/webhook/forward', async (req, res, next) => {
  try {
    const result = await client.post<unknown>(req.body.callbackUrl, req.body.payload, {
      timeout: 5_000,
    });
    res.json(result);
  } catch (err) {
    next(err);
  }
});

Vue 2

// src/plugins/http.ts
import Vue from 'vue';
import { client } from '@regcheq/http-client';

Vue.prototype.$http = client;

// src/main.ts
import './plugins/http';
// En un componente
export default Vue.extend({
  data() {
    return { empresa: null as Empresa | null, error: null as string | null };
  },
  async created() {
    try {
      this.empresa = await this.$http.get<Empresa>(`${process.env.VUE_APP_API_URL}/empresa/${this.id}`);
    } catch (err) {
      if (err instanceof HttpClientError) {
        this.error = `Error ${err.statusCode}`;
      }
    }
  },
});

En Vuex:

import { client } from '@regcheq/http-client';

export const actions = {
  async fetchEmpresa({ commit }, id: string) {
    const empresa = await client.get<Empresa>(`${process.env.VUE_APP_API_URL}/empresa/${id}`);
    commit('SET_EMPRESA', empresa);
  },
};

React

// src/hooks/useEmpresa.ts
import { useState, useEffect } from 'react';
import { client, HttpClientError } from '@regcheq/http-client';

export function useEmpresa(id: string) {
  const [empresa, setEmpresa] = useState<Empresa | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    client.get<Empresa>(`${import.meta.env.VITE_API_URL}/empresa/${id}`)
      .then(setEmpresa)
      .catch((err) => {
        if (err instanceof HttpClientError) {
          setError(`Error ${err.statusCode}`);
        }
      })
      .finally(() => setLoading(false));
  }, [id]);

  return { empresa, error, loading };
}

Con React Query:

import { useQuery, useMutation } from '@tanstack/react-query';
import { client } from '@regcheq/http-client';

export function useEmpresaQuery(id: string) {
  return useQuery({
    queryKey: ['empresa', id],
    queryFn: () => client.get<Empresa>(`${import.meta.env.VITE_API_URL}/empresa/${id}`),
  });
}

export function useCrearEmpresa() {
  return useMutation({
    mutationFn: (data: CreateEmpresaDto) =>
      client.post<Empresa>(`${import.meta.env.VITE_API_URL}/empresa`, data),
  });
}

Manejo de errores

Todos los errores (HTTP 4xx/5xx y de red) se normalizan en HttpClientError:

import { HttpClientError } from '@regcheq/http-client';

try {
  const data = await client.get<Empresa>(`${process.env.API_MAIN}/empresa/123`);
} catch (err) {
  if (err instanceof HttpClientError) {
    console.log(err.statusCode);     // 404, 500, 0 (error de red)
    console.log(err.body);           // body parseado si era JSON, string si no
    console.log(err.isNetworkError); // true si no hubo respuesta del servidor
    console.log(err.message);        // 'HTTP 404'
  }
}

Diferencias con axios:

// ANTES (axios)
} catch (err) {
  if (axios.isAxiosError(err)) {
    console.log(err.response?.status);
    console.log(err.response?.data);
  }
}

// DESPUÉS (@regcheq/http-client)
} catch (err) {
  if (err instanceof HttpClientError) {
    console.log(err.statusCode);
    console.log(err.body);
  }
}

Configuración de timeouts

Por variable de entorno (aplica a todos los clientes):

HTTP_TIMEOUT_MS=10000

Al crear un cliente dedicado (sobreescribe la variable de entorno):

const api = createClient(process.env.API_LENTA!, { timeout: 30_000 });

Por llamada individual:

await client.post('/upload', file, { timeout: 60_000 });

Prioridad: llamada > cliente dedicado > variable de entorno > default (10 segundos).

Configuración de retries

Por defecto no hay retries. Los retries solo aplican a errores de red (isNetworkError = true), nunca a errores HTTP (4xx/5xx).

// Sin retries (default)
const api = createClient(process.env.API_URL!);

// Con 2 retries para un servicio con intermitencia
const apiConRetry = createClient(process.env.API_LISTAS!, { retries: 2 });

El delay entre reintentos usa backoff exponencial: 200ms, 400ms, 800ms...

Por variable de entorno:

HTTP_RETRIES=2
HTTP_RETRY_DELAY_MS=200

Correlation ID (propagación automática)

En servicios que reciben un x-request-id y hacen llamadas a otros servicios, usar runWithCorrelationId para propagarlo automáticamente:

import { runWithCorrelationId } from '@regcheq/http-client';

// En el middleware de tu servicio (NestJS/Express/LoopBack)
app.use((req, res, next) => {
  const requestId = req.headers['x-request-id'] as string ?? crypto.randomUUID();
  runWithCorrelationId(requestId, () => next());
});

Todas las llamadas HTTP dentro de ese contexto incluirán el x-request-id automáticamente.

Migración desde axios

Llamada directa:

// ANTES
import axios from 'axios';
const response = await axios.get(`${process.env.API_FILES}/archivo/${id}`);
const data = response.data; // axios envuelve en { data }

// DESPUÉS
import { client } from '@regcheq/http-client';
const data = await client.get<Archivo>(`${process.env.API_FILES}/archivo/${id}`);
// retorna el body directamente — quitar .data

axios.create() → createClient():

// ANTES
const api = axios.create({ baseURL: process.env.API_FILES, timeout: 8000 });
const response = await api.post('/archivo', payload);
const data = response.data;

// DESPUÉS
import { createClient } from '@regcheq/http-client';
const api = createClient(process.env.API_FILES!, { timeout: 8000 });
const data = await api.post<UploadResult>('/archivo', payload);

Diferencias clave:

| Comportamiento | axios | @regcheq/http-client | |---|---|---| | Respuesta exitosa | response.data | valor directo | | Error HTTP | err.response.status + err.response.data | err.statusCode + err.body | | Body en POST/PUT/PATCH | segundo argumento | segundo argumento | | Content-Type | manual | automático si body es objeto | | Timeout | { timeout: ms } | { timeout: ms } | | Cancelación | CancelToken (deprecated) | { signal: AbortSignal } |

Variables de entorno disponibles

Todos tienen valores por defecto — solo definir si se necesita sobreescribir:

HTTP_TIMEOUT_MS=10000          # Timeout global en ms (default: 10000)
HTTP_MAX_CONNECTIONS=10        # Conexiones por pool (default: 10, solo Node.js)
HTTP_RETRIES=0                 # Reintentos ante error de red (default: 0)
HTTP_RETRY_DELAY_MS=200        # Delay base del backoff exponencial (default: 200)
HTTP_LOG_REQUESTS=false        # Log de cada request/response (default: false)
HTTP_CORRELATION_HEADER=x-request-id  # Header de correlación (default: x-request-id)

Notas de rendimiento en Node 22

Esta librería aplica automáticamente los fixes necesarios para Node 22:

  • Keep-alive: las conexiones TCP se reutilizan (undici Pool). Con axios sin esta librería cada request abría y cerraba una conexión, causando +100-500ms de overhead por request en K8s.
  • DNS ipv4first: Node 22 cambió el orden DNS a verbatim, causando que en Kubernetes los lookups IPv6 fallaran primero y después hicieran fallback a IPv4 (+50-200ms en DNS frío). Esta librería revierte al comportamiento de Node 18.

Estos dos fixes se aplican globalmente al importar la librería — no requieren configuración adicional.

Versioning (SemVer)

Este paquete sigue SemVer:

  • PATCH: fixes internos sin cambios de API.
  • MINOR: nuevas funcionalidades compatibles.
  • MAJOR: cambios que rompen compatibilidad.

Commits (Conventional Commits)

El release automático usa Conventional Commits. Ejemplos:

  • feat: agregar soporte para FormData → MINOR
  • fix: corregir timeout en pool de conexiones → PATCH
  • feat!: cambiar API de createClient → MAJOR

Tipos comunes: feat, fix, chore, docs, refactor, test.