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

v6.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.

Migração 5.x → 6.0.0

A versão 6 muda a API de logs (logging) e adiciona duas opções de requisição. Requisições que não usam logging não são afetadas.

  1. Logar passou a depender do resultado da chamada. Em 5.x, logRequest ou logResponse ligavam o log. Agora é preciso dizer quando logar:

    // 5.x
    logging: { logRequest: true, logResponse: true }
    
    // 6.0.0
    logging: { logOnSuccess: true, logOnError: true, logRequest: true, logResponse: true }

    Quem só quer o caso de erro usa logging: { logOnError: true, logRequest: true, logResponse: true }.

  2. Uma entrada de log por chamada, não por tentativa. Em 5.x, cada tentativa gerava um log. Agora a falha só é logada quando todas as tentativas falham, e um retry que dá certo gera somente o log de sucesso.

  3. logging pode ser configurado no cliente. As opções da requisição vencem as do cliente, campo a campo. Um valor undefined na requisição não apaga o valor do cliente. Os context são mesclados. As listas de redact são somadas, cada uma com a lista de mesmo nome:

    • redactRequestFields final = redactRequestFields do cliente + redactRequestFields da requisição
    • redactResponseFields final = redactResponseFields do cliente + redactResponseFields da requisição

    As duas listas continuam separadas, como na 5.x: redactRequestFields mascara params e requestBody, e redactResponseFields mascara responseBody.

  4. O log de falha agora traz dados reais. Em 5.x, statusCode e responseBody vinham undefined em qualquer falha; agora são preenchidos a partir da resposta do erro, junto de errorCode e errorType. O responseBody da falha também passa por redactResponseFields.

  5. Campos do log: numOfAttempts e timeout são novos, e durationInMs passa a ser a duração total da chamada (incluindo os retries), igual à métrica. Asserções de teste que comparam o objeto neHttp inteiro precisam incluí-los.

  6. RequestOptions aceita responseType e responseEncoding, repassados ao axios. Veja Download de Arquivos Binários.

  7. O log de falha usa warn por padrão. Use errorLevel: 'error' para manter o nível anterior.

  8. Uma falha ao enviar a métrica não quebra mais a chamada. Em 5.x, um erro no envio da métrica (ex.: URL relativa, que new URL não aceita) rejeitava a chamada, mesmo com a requisição bem-sucedida, e escondia o ClientError original. Agora o erro é logado com logger.error e a chamada mantém o seu resultado.

  9. redactRequestFields também mascara os pathParams na URL logada. Em 5.x, a URL do log (neHttp.url e a mensagem) mostrava os valores reais dos pathParams. Agora um pathParam cujo nome está em redactRequestFields aparece como [REDACTED] na URL logada. A requisição continua usando a URL real.

  10. ClientError não quebra mais com um responseData que não pode ser serializado. Em 5.x, um corpo de erro cíclico fazia o construtor do ClientError lançar um TypeError, e a chamada rejeitava com ele. Agora a mensagem do erro mostra Response Data: [unserializable], e responseData continua com o valor original.

O mascaramento continua igual ao da 5.x: a comparação dos nomes de campo é exata. Nada mais mudou: HttpClient, retries, campos das métricas e DNS cache seguem iguais.

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.example.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://api.example.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

Download de Arquivos Binários

Para baixar um arquivo (por exemplo, uma etiqueta em PDF no S3), use responseType: 'arraybuffer'. Assim, data é um Buffer com os bytes do arquivo. Sem essa opção, o axios converte a resposta em texto e corrompe o arquivo.

const response = await httpClient.get<Buffer>(s3Url, {
    responseType: 'arraybuffer',
    timeout: 5000,
    validateStatus: status => status === 200,
    logging: { logResponse: false, context: { trackingCode } },
});

responseEncoding é repassado ao axios e só tem efeito em respostas de texto: com responseType: 'arraybuffer' o axios o ignora.

Se o cliente estiver configurado com logResponse: true, use logResponse: false nos downloads binários. Sem isso, o arquivo inteiro vai para o log.

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 |

Uma falha ao enviar a métrica não afeta a chamada: a requisição retorna o seu resultado (ou rejeita com o erro original) e a falha é logada com logger.error, com a mensagem HTTP request metric failed {METHOD}. Esse log traz só o erro como texto (error, ex.: TypeError: Invalid URL): a URL e o objeto de erro não são logados, porque a URL pode conter tokens na query string.

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.example.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 chamada usando o logger informado no construtor. O log é desabilitado por padrão e pode ser configurado no cliente (valendo para todas as requisições) e sobrescrito por requisição, através da opção logging:

type LogLevel = 'debug' | 'info' | 'warn' | 'error';

type LoggingOptions = {
    logOnSuccess?: boolean;
    logOnError?: boolean;
    successLevel?: LogLevel;
    errorLevel?: LogLevel;
    logRequest?: boolean;
    logResponse?: boolean;
    redactRequestFields?: string[];
    redactResponseFields?: string[];
    context?: Record<string, unknown>;
};

| Campo | Tipo | Padrão | Descrição | | ---------------------- | ------------------------- | --------- | --------------------------------------------------------------------------- | | logOnSuccess | boolean | false | Emite o log quando a chamada é bem-sucedida | | logOnError | boolean | false | Emite o log quando todas as tentativas da chamada falham | | successLevel | LogLevel | 'info' | Nível usado no log de sucesso | | errorLevel | LogLevel | 'warn' | Nível usado no log de falha | | logRequest | boolean | false | Inclui os dados do request (params e requestBody) no log | | logResponse | boolean | false | Inclui o corpo da resposta (responseBody) no log | | redactRequestFields | string[] | — | Campos mascarados nos params, no corpo da requisição e nos pathParams da URL logada | | redactResponseFields | string[] | — | Campos mascarados no corpo da resposta | | context | Record<string, unknown> | — | Campos extras do domínio de quem chama (ex.: carrierCode, trackingCode) |

O log só é emitido quando logOnSuccess (em caso de sucesso) ou logOnError (em caso de falha) for true. Para logar somente falhas, use logOnSuccess: false e logOnError: true.

Se o logger informado não implementar o nível escolhido (warn e debug são opcionais na interface Logger), o log é emitido em info.

Configuração no cliente (uma vez, valendo para todas as requisições):

const httpClient = new LogisticsHttpClient(metricHandler, logger, {
    metrics: { enabled: true, originService: 'my-service' },
    logging: {
        logOnSuccess: false,
        logOnError: true,
        logRequest: true,
        logResponse: true,
        redactRequestFields: ['password', 'token', 'email', 'phone'],
        redactResponseFields: ['password', 'token', 'email', 'phone'],
    },
});

Sobrescrita por requisição:

await httpClient.get(labelUrl, {
    validateStatus: status => status < 400,
    logging: {
        logResponse: false,
        context: { trackingCode },
    },
});

As opções da requisição são combinadas com as do cliente:

  • cada campo informado na requisição vence o valor do cliente; um valor undefined não apaga o valor do cliente;
  • cada lista de redact do cliente é somada à lista de mesmo nome da requisição (a requisição só adiciona campos): redactRequestFields com redactRequestFields, e redactResponseFields com redactResponseFields;
  • os campos de context do cliente e da requisição são mesclados.

O que é logado:

Ao final de cada chamada é emitida uma única entrada consolidada, com a mensagem HTTP Payload {METHOD} {url}. Com retry, a falha só é logada quando todas as tentativas falham; se um retry dá certo, só o sucesso é logado. 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, com redactRequestFields aplicada) | | statusCode | sempre | Código HTTP da resposta (undefined quando não houve resposta) | | durationInMs | sempre | Duração total da chamada em milissegundos, incluindo os retries | | numOfAttempts | sempre | Número de tentativas feitas | | timeout | sempre | Timeout configurado para a requisição | | errorCode | em falha | Código do erro da última tentativa (ex.: ETIMEDOUT) | | errorType | em falha | instability ou other | | params | logRequest | Query params (com redaction aplicado) | | requestBody | logRequest | Body da requisição (com redaction aplicado) | | responseBody | logResponse | Corpo da resposta (com redaction aplicado) | | context | — | Os campos de context são espalhados dentro de neHttp |

Os nomes dos campos acima são reservados: uma chave de context com um desses nomes é sempre descartada, mesmo quando o campo não aparece no log (ex.: responseBody com logResponse: false, ou errorType em um sucesso).

Exemplo do objeto logado em uma falha:

logger.warn(
    {
        neHttp: {
            carrierCode: 'some-carrier',
            method: 'post',
            url: 'https://api.example.com/shipments',
            statusCode: 422,
            durationInMs: 187,
            numOfAttempts: 1,
            timeout: 4400,
            errorCode: 'ERR_BAD_REQUEST',
            errorType: 'other',
            requestBody: { origin: { name: '[REDACTED]', postalCode: '11550' } },
            responseBody: { error: { message: 'Invalid phone number' } },
        },
    },
    'HTTP Payload POST https://api.example.com/shipments',
);

Redaction

redactRequestFields (aplicada aos params, ao corpo da requisição e aos pathParams da URL logada) e redactResponseFields (aplicada ao corpo da resposta) são listas de nomes de campos. Manter as duas separadas permite, por exemplo, mascarar dados pessoais que só existem na requisição sem esconder os campos da resposta usados em diagnóstico. A comparação, igual nas duas:

  • é exata e diferencia maiúsculas de minúsculas: phone mascara phone, mas não Phone, phone_number nem phoneNumber (liste cada nome que deve ser mascarado);
  • percorre objetos e arrays aninhados, substituindo o valor por [REDACTED];
  • não altera valores que não são objetos simples nem arrays, como Buffer e Date;
  • substitui corpos de resposta em stream (responseType: 'stream') por [STREAM], sem consumir o stream;
  • protege contra estruturas cíclicas ([CIRCULAR]) e contra profundidade excessiva ([MAX_DEPTH], acima de 12 níveis).

O mascaramento só se aplica a objetos e arrays: corpos em texto (XML, HTML, JSON não parseado) e a URL são logados sem alteração.

Sem essas listas, nada é mascarado. Headers nunca são logados.

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.