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

@tiendanube/logistics-http-client

v5.0.0

Published

Package responsible for executing http requests

Readme

Biblioteca responsável por executar requisições HTTP com mecanismos de retry, métricas e tratamento de erros integrados para os microsserviços de logística.

Instalação

Execute o comando:

yarn add @tiendanube/logistics-http-client

❗ Atenção: é necessário estar autenticado no registry do NPM da Nuvemshop para instalar o pacote.

Utilização

A biblioteca exporta uma interface HttpClient e uma implementação para essa interface chamada LogisticsHttpClient, que recebe a injeção de uma instância de MetricHandler, um Logger e um objeto de configuração. A interface HttpClient abstrai as ações disponíveis para execução de requisições HTTP.

O cliente HTTP fornece suporte integrado para:

  • Mecanismos de retry com exponential backoff
  • Emissão automática de métricas para monitoramento de requisições
  • Log consolidado de request e response configurável por requisição, com redaction de campos sensíveis
  • Tratamento de erros com tipos de erro customizados
  • Substituição de parâmetros de path para URLs dinâmicas
  • Pool de conexões TCP com reuso automático para melhor performance
  • Cache de DNS no nível da aplicação para reduzir latência e aumentar resiliência

Para começar a usar o cliente HTTP, você deve criar uma instância de LogisticsHttpClient fornecendo um metric handler, um logger e a configuração:

import { LogisticsHttpClient } from '@tiendanube/logistics-http-client';
import { LoggerMetricHandler } from '@tiendanube/logistics-metric-handler';

const logger = new Logger();
const metricHandler = new LoggerMetricHandler(logger);

const httpClient = new LogisticsHttpClient(metricHandler, logger, {
    metrics: {
        enabled: true,
        originService: 'my-service',
    },
});

❗ Atenção: É obrigatório informar os parâmetros metricHandler, logger e o objeto de configuração com metrics para a criação de uma instância de LogisticsHttpClient.

O parâmetro logger deve implementar a interface Logger, compatível com loggers comuns como pino ou o Logger do NestJS:

interface Logger {
    info(obj: unknown, msg?: string, ...args: any[]): void;
    error(obj: unknown, msg?: string, ...args: any[]): void;
}

O logger só é efetivamente acionado quando o log está habilitado (veja Configuração de Logs). Caso contrário, ele permanece inativo.

A interface HttpClient disponibiliza os seguintes métodos HTTP:

| Método | Descrição | | -------------------------------------------------------------------------------------------- | ----------------------------------------------- | | get<T>(url: string, options: RequestOptions): Promise<Response<T>> | Executa uma requisição GET | | post<T>(url: string, data: any, options: RequestOptions): Promise<Response<T>> | Executa uma requisição POST com body | | patch<T>(url: string, data: any, options: RequestOptions): Promise<Response<T>> | Executa uma requisição PATCH com body | | put<T>(url: string, data: any, options: RequestOptions): Promise<Response<T>> | Executa uma requisição PUT com body | | delete<T>(url: string, options: RequestOptions): Promise<Response<T>> | Executa uma requisição DELETE | | request<T>(method: HttpMethod, url: string, options: RequestOptions): Promise<Response<T>> | Executa uma requisição com qualquer método HTTP |

Todos os métodos retornam uma Promise<Response<T>> onde Response contém as propriedades data e status.

Exemplo de Uso Básico

O trecho abaixo exemplifica a utilização direta da interface HttpClient para executar requisições HTTP:

import { LogisticsHttpClient } from '@tiendanube/logistics-http-client';
import { LoggerMetricHandler } from '@tiendanube/logistics-metric-handler';

const logger = new Logger();
const metricHandler = new LoggerMetricHandler(logger);

const httpClient = new LogisticsHttpClient(metricHandler, logger, {
    metrics: {
        enabled: true,
        originService: 'quotation-service',
    },
});

async function fetchUserData(userId: string) {
    try {
        const response = await httpClient.get(`https://api.example.com/users/${userId}`, {
            headers: {
                'Authorization': 'Bearer token',
                'Content-Type': 'application/json',
            },
            timeout: 5000,
            validateStatus: status => status < 400,
        });

        return response.data;
    } catch (error) {
        if (error instanceof ClientError) {
            console.error('HTTP Error:', error.statusCode, error.responseData); // Mais informações na seção "Tratamento de erros"
        }
        throw error;
    }
}

Utilização com Configuração de Retry

A biblioteca fornece mecanismos poderosos de retry com exponential backoff. Você pode configurar o comportamento de retry para diferentes cenários:

async function fetchWithRetry() {
    const response = await httpClient.get('https://api.external-service.com/data', {
        headers: { Authorization: 'Bearer token' },
        timeout: 10000,
        validateStatus: status => status < 400,
        retry: {
            retryOn502: true,
            retryOnTimeout: true,
            retryOnAbort: true,
            retryOnStatusCodes: [503, 504],
            maxAttempts: 5,
            startingDelay: 300,
            maxDelayBetweenAttempts: 2000,
            timeMultiple: 2,
            delayFirstAttempt: false,
            jitter: 'full',
        },
        pathParams: {
            storeId: '12345',
            orderId: 'order-123',
        },
        isInstabilityError: (errorCode, statusCode, responseData) => {
            return statusCode === 429 || responseData?.error === 'rate_limit';
        },
    });

    return response.data;
}

Exemplo de Uso com NestJS

Caso a aplicação em que esta biblioteca foi instalada seja baseada no framework NestJS, o exemplo abaixo pode ser utilizado como referência para sua correta utilização.

Idealmente a biblioteca será injetada como dependência da classe que está executando requisições HTTP. Para isso pode-se configurar a classe e um arquivo de módulo para realizar a injeção.

// quotation.service.ts

import { HttpClient, LogisticsHttpClient } from '@tiendanube/logistics-http-client';

@Injectable()
export class QuotationService {
    constructor(
        @Inject(LogisticsHttpClient.name)
        private readonly httpClient: HttpClient,
    ) {}

    async getExternalQuotation(storeId: string, quotationData: any) {
        const response = await this.httpClient.post(
            'https://external-api.com/stores/${storeId}/quotations',
            quotationData,
            {
                headers: {
                    'Authorization': 'Bearer secret-token',
                    'Content-Type': 'application/json',
                },
                timeout: 15000,
                validateStatus: status => status < 400,
                pathParams: { storeId },
                retry: {
                    retryOn502: true,
                    retryOnTimeout: true,
                    maxAttempts: 3,
                },
            },
        );

        return response.data;
    }
}

Então, é necessário configurar o módulo do Nest para injetar uma instância do HttpClient para o serviço que a utilizará:

// quotation.module.ts
import { LogisticsHttpClient, LOGISTICS_HTTP_CLIENT_METRIC_COMPONENT_NAME } from '@tiendanube/logistics-http-client';

@Module({
    providers: [
        {
            provide: LogisticsHttpClient.name,
            useFactory: async (logger: PinoLogger, config: MetricHandlerConfig) =>
                new LogisticsHttpClient(
                    new StatsDMetricHandler(config.statsD, logger, LOGISTICS_HTTP_CLIENT_METRIC_COMPONENT_NAME),
                    logger,
                    {
                        metrics: {
                            enabled: false,
                            originService: 'ne-ap-com-k8s-my-service',
                        },
                    },
                ),
            inject: [PinoLogger, metricHandlerConfig.KEY],
        },
        QuotationService,
    ],
    exports: [QuotationService],
})
export class QuotationModule {}

Configurações Avançadas

Tratamento de Erros

A biblioteca fornece uma classe customizada ClientError que estende o Error padrão com propriedades específicas para HTTP:

import { ClientError, ErrorType } from '@tiendanube/logistics-http-client';

try {
    await httpClient.get('https://api.example.com/data', options);
} catch (error) {
    if (error instanceof ClientError) {
        console.log('Method:', error.method);
        console.log('URL:', error.url);
        console.log('Status Code:', error.statusCode);
        console.log('Response Data:', error.responseData);
        console.log('Error Code:', error.errorCode);
        console.log('Error Type:', error.errorType);
    }
}

Parâmetros de Path

Use parâmetros de path para construção dinâmica de URLs:

const response = await httpClient.get('https://api.example.com/stores/${storeId}/orders/${orderId}', {
    pathParams: {
        storeId: '12345',
        orderId: 'order-67890',
    },
    validateStatus: status => status < 400,
});
// URL becomes: https://api.example.com/stores/12345/orders/order-67890

Configuração de Métricas

Controle a emissão de métricas por requisição:

// Disable metrics for a specific request
await httpClient.get('https://api.example.com/health', {
    validateStatus: status => status < 400,
    metricEnabled: false,
});

Métrica Emitida

A biblioteca emite automaticamente a métrica request (componente: logistics-http-client) para todas as requisições HTTP, a menos que desabilitada explicitamente.

Campos da métrica:

| Campo | Tipo | Descrição | | ----------------- | ----------- | ---------------------------------------------------------- | | duration | number | Duração total da requisição em milissegundos | | success | boolean | Se a requisição foi bem-sucedida | | statusCode | number | Código HTTP de resposta (200, 404, 500, etc.) | | httpMethod | string | Método HTTP usado (GET, POST, PUT, PATCH, DELETE) | | numOfAttempts | number | Número de tentativas realizadas (com retry) | | baseUrl | string | URL base (origin) do servidor de destino | | path | string | Path da requisição | | originService | string | Nome do serviço que originou a requisição | | externalStoreId | string | ID da loja externa (extraído automaticamente se disponível)| | errorType | ErrorType | Tipo de erro (Instability ou Other) se houver falha |

Exemplo de uso com StatsD:

import { StatsDMetricHandler } from '@tiendanube/logistics-metric-handler';

const metricHandler = new StatsDMetricHandler(
    statsDConfig, 
    logger, 
    'logistics-http-client'
);

const httpClient = new LogisticsHttpClient(metricHandler, logger, {
    metrics: {
        enabled: true,
        originService: 'quotation-service',
    },
});

// Cada requisição emitirá:
// logistics-http-client.request com todos os campos acima
await httpClient.get('https://api.external.com/data', options);

Essa métrica permite monitoramento completo de:

  • Latência e performance de APIs externas
  • Taxa de sucesso/erro por endpoint
  • Impacto de retries nas requisições
  • Identificação de serviços instáveis

Configuração de Logs

Além das métricas, a biblioteca pode logar o request e o response de cada requisição usando o logger informado no construtor. O log é desabilitado por padrão e é configurado por requisição, através da opção logging no RequestOptions:

type LoggingOptions = {
    logRequest?: boolean;
    logResponse?: boolean;
    redactRequestFields?: string[];
    redactResponseFields?: string[];
};

| Campo | Tipo | Descrição | | ---------------------- | ---------- | ------------------------------------------------------------------------------- | | logRequest | boolean | Quando true, inclui os dados do request (params e requestBody) no log | | logResponse | boolean | Quando true, inclui o corpo da resposta (responseBody) no log | | redactRequestFields | string[] | Campos a serem mascarados nos dados do request | | redactResponseFields | string[] | Campos a serem mascarados no corpo da resposta |

O log só é emitido quando logRequest ou logResponse for true.

Exemplo:

await httpClient.post('https://api.example.com/login', body, {
    validateStatus: status => status < 400,
    logging: {
        logRequest: true,
        logResponse: true,
        redactRequestFields: ['password', 'token'],
        redactResponseFields: ['accessToken'],
    },
});

O que é logado:

Ao final da requisição (com sucesso ou falha) é emitida uma única entrada info consolidada, com a mensagem HTTP Payload {METHOD} {url}. Os metadados são agrupados sob o nó neHttp:

| Campo | Presente quando | Descrição | | -------------- | --------------- | ------------------------------------------------------ | | method | sempre | Método HTTP | | url | sempre | URL resolvida (após substituição de pathParams) | | statusCode | sempre | Código HTTP da resposta (undefined em falha sem resposta) | | durationInMs | sempre | Duração da requisição em milissegundos | | params | logRequest | Query params (com redaction aplicado) | | requestBody | logRequest | Body da requisição (com redaction aplicado) | | responseBody | logResponse | Corpo da resposta (com redaction aplicado) |

Exemplo do objeto logado:

logger.info(
    {
        neHttp: {
            method: 'post',
            url: 'https://api.example.com/login',
            statusCode: 200,
            durationInMs: 42,
            params: { foo: 'bar' },
            requestBody: { username: 'someUser', password: '[REDACTED]' },
            responseBody: { id: 1, accessToken: '[REDACTED]' },
        },
    },
    'HTTP Payload POST https://api.example.com/login',
);

❗ Atenção: por questões de segurança, os headers da requisição nunca são logados (para evitar vazamento de tokens de autenticação). Os campos listados em redactRequestFields/redactResponseFields são substituídos por '[REDACTED]' — inclusive em objetos aninhados e em itens de arrays. Ainda assim, params, requestBody e responseBody podem conter dados sensíveis: habilite logRequest/logResponse com cautela.

Otimizações de Performance

A biblioteca implementa otimizações automáticas de rede para maximizar performance e resiliência em ambientes de alta carga.

Cache de DNS

Por padrão, a biblioteca implementa cache de DNS no nível da aplicação usando cacheable-lookup. Isso traz diversos benefícios:

  • Reduz latência: DNS lookups são cacheados por até 5 minutos (configurável)
  • Aumenta resiliência: Mantém cache mesmo durante falhas temporárias de DNS
  • Reduz carga: Menos consultas aos servidores DNS
  • Suporta TTL: Respeita os TTLs retornados pelos servidores DNS

Configuração padrão:

const httpClient = new LogisticsHttpClient(metricHandler, logger, {
    metrics: {
        enabled: true,
        originService: 'my-service',
    },
    dnsCache: {
        enabled: true,
        maxTtlInSeconds: 300,
        errorTtlInSeconds: 0.15,
    },
});

Desabilitar cache de DNS (não recomendado):

const httpClient = new LogisticsHttpClient(metricHandler, logger, {
    metrics: {
        enabled: true,
        originService: 'my-service',
    },
    dnsCache: {
        enabled: false,
    },
});

Como funciona

O cache de DNS usa dns.resolve4/resolve6 (consulta UDP direta) para obter IPs e TTLs dos servidores DNS:

  1. Primeira requisição: Consulta DNS direta → Cache com TTL
  2. Requisições subsequentes: Retorna do cache (latência ~0ms)
  3. Fallback inteligente: Em caso de ENOTFOUND, usa dns.lookup (que verifica /etc/hosts e cache do OS)

Isso garante compatibilidade com configurações locais (como /etc/hosts) e DNS interno do Kubernetes.

Pool de Conexões TCP

A biblioteca mantém um pool de conexões TCP reutilizáveis para cada host, eliminando a necessidade de handshakes TCP repetidos:

  • Connection reuse: Até 128 conexões simultâneas por host
  • Keep-alive: Conexões permanecem abertas para reutilização
  • Pool management: Até 50 conexões idle mantidas no pool

Configuração padrão:

const httpClient = new LogisticsHttpClient(metricHandler, logger, {
    metrics: {
        enabled: true,
        originService: 'my-service',
    },
    agent: {
        keepAlive: true,
        keepAliveMsecs: 1000,
        maxSockets: 128,
        maxFreeSockets: 50,
        timeout: 30000,
    },
});

Parâmetros do Agent:

| Parâmetro | Padrão | Descrição | | ------------------- | ------ | --------------------------------------------------------------- | | keepAlive | true | Mantém conexões abertas para reuso | | keepAliveMsecs | 1000 | Intervalo (ms) para enviar TCP keepalive probes | | maxSockets | 128 | Máximo de conexões simultâneas por host | | maxFreeSockets | 50 | Máximo de conexões idle mantidas no pool | | timeout | 30000| Tempo (ms) que uma conexão idle permanece no pool antes de fechar |

Contribuindo

Para entender como funciona o processo de geração de novas versões para essa biblioteca, consulte a página CONTRIBUTING.