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

@almvasiliev/http-client

v1.0.0

Published

Lightweight, production-ready HTTP client for Node.js ≥18 built on native Fetch (Undici). Supports streaming, interceptors, retries, pluggable auth, and seamless NestJS integration — fully typed with zero runtime dependencies.

Downloads

26

Readme

@almvasiliev/http-client

Lightweight, production-ready HTTP client for Node.js ≥18 built on native Fetch (Undici). Supports streaming, interceptors, retries, pluggable auth, and seamless NestJS integration — fully typed with zero runtime dependencies.

Installation

npm install @almvasiliev/http-client

Features

| Feature | Description | |---|---| | Native fetch | Uses built-in fetch (undici) | | Streaming | ReadableStream<Uint8Array> for OpenAI-compatible SSE interfaces | | Timeout | Configurable HTTP timeout (default 30 000 ms) | | Retry | Exponential backoff with configurable conditions and statuses | | Interceptors | Pipeline onRequest → onResponse → onError | | Logging | Independent control: all requests (default OFF), errors (default ON) | | Sanitize headers | Masking sensitive headers in logs (default list is empty) | | Auth providers | BasicAuth, BearerAuth and extensible HttpAuthProvider interface | | Custom logger | Pass your own logger via the HttpLogger interface | | Dual entry | Framework-agnostic (./) and NestJS (./nestjs) supports |


Configuration

All parameters are passed when initializing the client (HttpClient) or the module (HttpModule.forRoot() / forRootAsync()).

Full parameter list

| Parameter | Type | Default | Description | |---|---|---|---| | baseUrl | string | — | Base URL for all requests | | timeout | number | 30000 | HTTP timeout in milliseconds | | defaultHeaders | Record<string, string> | — | Default headers for all requests | | logging | boolean \| HttpLoggingOptions | { all: false, errors: true } | Logging configuration (see below) | | logger | HttpLogger | Console | Custom logger (native HttpClient only) | | retry | HttpRetryOptions | see below | Retry configuration (see below) |

Retry (retry)

| Parameter | Type | Default | Description | |---|---|---|---| | maxRetries | number | 0 | Maximum number of retry attempts | | retryDelay | number | 1000 | Base delay between attempts (ms) | | retryableStatuses | number[] | [408, 429, 500, 502, 503, 504] | HTTP statuses that trigger a retry | | exponentialBackoff | boolean | false | Exponential delay growth | | retryCondition | (error) => boolean | — | Custom retry condition | | onRetry | (count, error) => void | — | Callback on each retry |

Logging (logging)

| Parameter | Type | Default | Description | |---|---|---|---| | all | boolean | false | Log all requests and responses | | errors | boolean | true | Log errors | | sanitizeHeaders | string[] | [] | List of headers to mask in logs (case-insensitive) |

Shortcuts:

  • logging: true — enable everything ({ all: true, errors: true })
  • logging: false — disable everything ({ all: false, errors: false })

Authorization (Auth Providers)

The package provides an extensible authorization system based on the HttpAuthProvider interface.

Built-in providers

| Provider | Description | |---|---| | BasicAuth | HTTP Basic Authentication (Basic base64(username:password)) | | BearerAuth | Bearer token (Bearer <token>) | | AuthInterceptor | Interceptor for automatic injection of the Authorization header |


Native usage

import { HttpClient } from '@almvasiliev/http-client';

const client = new HttpClient({
  baseUrl: 'https://api.example.com',
  timeout: 5000,
  logging: {
    all: true,
    errors: true,
    sanitizeHeaders: ['authorization', 'x-api-key'],
  },
  retry: {
    maxRetries: 3,
    retryDelay: 1000,
    retryableStatuses: [408, 429, 500, 502, 503, 504],
    exponentialBackoff: true,
  },
});

// GET
const { data } = await client.get<User[]>('/users');

// POST
const { data: created } = await client.post<User>('/users', { name: 'John' });

// Streaming (OpenAI SSE)
const { stream, status } = await client.stream({
  url: '/v1/chat/completions',
  method: 'POST',
  data: { model: 'gpt-4', messages: [...], stream: true },
});

const reader = stream!.getReader();
const decoder = new TextDecoder();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  console.log(decoder.decode(value));
}

Timeout

// Global timeout of 10 seconds
const client = new HttpClient({ timeout: 10_000 });

// Override at request level
await client.get('/slow-endpoint', { timeout: 60_000 });

// No timeout (for streaming — default timeout: 0)
const { stream } = await client.stream({ url: '/v1/completions', method: 'POST', data: body });

Retry

// No retry (default)
const client = new HttpClient({ baseUrl: 'https://api.example.com' });

// With retry: 3 attempts, exponential backoff
const client = new HttpClient({
  baseUrl: 'https://api.example.com',
  retry: {
    maxRetries: 3,
    retryDelay: 1000,
    exponentialBackoff: true,
  },
});

// Custom retryable statuses
const client = new HttpClient({
  retry: {
    maxRetries: 2,
    retryableStatuses: [429, 503],
  },
});

// Custom retry condition
const client = new HttpClient({
  retry: {
    maxRetries: 5,
    retryCondition: (error) => error.status === 429,
    onRetry: (count, error) => console.log(`Retry #${count}, status: ${error.status}`),
  },
});

// Override retry at request level
await client.get('/unstable', {
  retry: { maxRetries: 5, retryDelay: 2000 },
});

Logging: sanitizeHeaders

// Masking sensitive headers in logs
const client = new HttpClient({
  logging: {
    all: true,
    errors: true,
    sanitizeHeaders: ['authorization', 'x-api-key', 'cookie', 'set-cookie'],
  },
});

// In logs: headers: { authorization: '[REDACTED]', 'content-type': 'application/json' }

Logging: control

// Log everything (requests + responses + errors)
new HttpClient({ logging: true });

// Log errors only (default)
new HttpClient({ logging: { all: false, errors: true } });

// Everything disabled
new HttpClient({ logging: false });

// Requests/responses only, no errors
new HttpClient({ logging: { all: true, errors: false } });

Custom logger

import { HttpClient, type HttpLogger } from '@almvasiliev/http-client';

const myLogger: HttpLogger = {
  log: (message, data) => console.info(`[HTTP] ${message}`, data),
  error: (message, data) => console.error(`[HTTP ERROR] ${message}`, data),
};

const client = new HttpClient({
  baseUrl: 'https://api.example.com',
  logging: {
    all: true,
    errors: true,
    sanitizeHeaders: ['authorization'],
  },
  logger: myLogger,
});

Authorization BasicAuth

import { HttpClient, BasicAuth, AuthInterceptor } from '@almvasiliev/http-client';

const auth = new BasicAuth({ username: 'admin', password: 'secret' });

// Option 1: via defaultHeaders
const client = new HttpClient({
  baseUrl: 'https://api.example.com',
  defaultHeaders: {
    Authorization: auth.getAuthHeader(),
  },
});

// Option 2: via AuthInterceptor (recommended)
const client2 = new HttpClient({ baseUrl: 'https://api.example.com' });
client2.addInterceptor(new AuthInterceptor(auth));

// Option 3: per-request
const client3 = new HttpClient({ baseUrl: 'https://api.example.com' });
await client3.get('/protected', {
  headers: { Authorization: auth.getAuthHeader() },
});

Authorization BearerAuth

import { HttpClient, BearerAuth, AuthInterceptor } from '@almvasiliev/http-client';

const auth = new BearerAuth('my-jwt-token');

const client = new HttpClient({ baseUrl: 'https://api.example.com' });
client.addInterceptor(new AuthInterceptor(auth));

await client.get('/protected'); // Authorization: Bearer my-jwt-token

NestJS usage

import { HttpModule, HttpService } from '@almvasiliev/http-client/nestjs';

// app.module.ts — full configuration
@Module({
  imports: [
    HttpModule.forRoot({
      baseUrl: 'https://api.example.com',
      timeout: 5000,
      logging: {
        all: false,
        errors: true,
        sanitizeHeaders: ['authorization', 'x-api-key'],
      },
      retry: {
        maxRetries: 3,
        retryDelay: 1000,
        retryableStatuses: [408, 429, 500, 502, 503, 504],
        exponentialBackoff: true,
      },
    }),
  ],
})
export class AppModule {}

Async configuration

HttpModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    baseUrl: config.get('API_URL'),
    timeout: Number(config.get('HTTP_TIMEOUT', 30000)),
    logging: {
      all: config.get('HTTP_LOG_ALL') === 'true',
      errors: true,
      sanitizeHeaders: ['authorization', 'x-api-key', 'cookie'],
    },
    retry: {
      maxRetries: Number(config.get('HTTP_MAX_RETRIES', 0)),
      retryDelay: Number(config.get('HTTP_RETRY_DELAY', 1000)),
      retryableStatuses: [408, 429, 500, 502, 503, 504],
      exponentialBackoff: true,
    },
  }),
});

Service usage

@Injectable()
export class UserService {
  constructor(private readonly httpService: HttpService) {}

  async getUsers() {
    return this.httpService.get<User[]>('/users');
  }

  async streamCompletion(messages: Message[]) {
    const { stream } = await this.httpService.stream({
      url: '/v1/chat/completions',
      method: 'POST',
      data: { model: 'gpt-4', messages, stream: true },
    });
    return stream;
  }
}

Authorization

import { HttpModule, HttpService } from '@almvasiliev/http-client/nestjs';
import { BasicAuth, AuthInterceptor } from '@almvasiliev/http-client';

// Option 1: via defaultHeaders in forRoot
@Module({
  imports: [
    HttpModule.forRoot({
      baseUrl: 'https://api.example.com',
      defaultHeaders: {
        Authorization: new BasicAuth({ username: 'admin', password: 'secret' }).getAuthHeader(),
      },
    }),
  ],
})
export class AppModule {}

// Option 2: via AuthInterceptor in a service
@Injectable()
export class ApiService {
  constructor(private readonly httpService: HttpService) {
    const auth = new BasicAuth({ username: 'admin', password: 'secret' });
    this.httpService.addInterceptor(new AuthInterceptor(auth));
  }

  async getData() {
    return this.httpService.get('/protected');
  }
}

// Option 3: async configuration with credentials from ConfigService
HttpModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    baseUrl: config.get('API_URL'),
    defaultHeaders: {
      Authorization: new BasicAuth({
        username: config.get('API_USERNAME'),
        password: config.get('API_PASSWORD'),
      }).getAuthHeader(),
    },
  }),
});

Extension: custom Auth provider

Implement the HttpAuthProvider interface for any authorization type:

import {
  HttpClient,
  AuthInterceptor,
  type HttpAuthProvider,
} from '@almvasiliev/http-client';

// Example: OAuth2 with automatic token refresh
class OAuth2Auth implements HttpAuthProvider {
  private accessToken: string | null = null;
  private expiresAt = 0;

  constructor(
    private readonly clientId: string,
    private readonly clientSecret: string,
    private readonly tokenUrl: string,
  ) {}

  async getAuthHeader(): Promise<string> {
    if (!this.accessToken || Date.now() >= this.expiresAt) {
      await this.refreshToken();
    }
    return `Bearer ${this.accessToken}`;
  }

  private async refreshToken(): Promise<void> {
    const response = await fetch(this.tokenUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: this.clientId,
        client_secret: this.clientSecret,
      }),
    });
    const data = await response.json() as { access_token: string; expires_in: number };
    this.accessToken = data.access_token;
    this.expiresAt = Date.now() + data.expires_in * 1000 - 60_000; // refresh 1 min before expiry
  }
}

// Usage
const auth = new OAuth2Auth('client-id', 'client-secret', 'https://auth.example.com/token');
const client = new HttpClient({ baseUrl: 'https://api.example.com' });
client.addInterceptor(new AuthInterceptor(auth));

// AuthInterceptor calls getAuthHeader() before each request,
// the token is refreshed automatically upon expiry
await client.get('/protected');
// Example: API Key in a custom header
class ApiKeyAuth implements HttpAuthProvider {
  constructor(
    private readonly apiKey: string,
    private readonly headerName = 'X-API-Key',
  ) {}

  getAuthHeader(): string {
    return this.apiKey;
  }
}

// If you need a header other than Authorization, use an interceptor directly:
const client = new HttpClient({ baseUrl: 'https://api.example.com' });
client.addInterceptor({
  onRequest(config) {
    return {
      ...config,
      headers: { ...config.headers, 'X-API-Key': 'my-api-key-123' },
    };
  },
});

API

HttpClient / HttpService

| Method | Description | |---|---| | request<T>(config) | Full HTTP request | | stream(config) | Streaming request, returns ReadableStream | | get<T>(url, config?) | GET request | | post<T>(url, data?, config?) | POST request | | put<T>(url, data?, config?) | PUT request | | patch<T>(url, data?, config?) | PATCH request | | delete<T>(url, config?) | DELETE request | | head(url, config?) | HEAD request | | options<T>(url, config?) | OPTIONS request | | addInterceptor(interceptor) | Add an interceptor |

Auth

| Class / Interface | Description | |---|---| | HttpAuthProvider | Authorization interface (getAuthHeader(): string \| Promise<string>) | | BasicAuth | HTTP Basic Auth (Basic base64(username:password)) | | BearerAuth | Bearer token (Bearer <token>) | | AuthInterceptor | Interceptor for auto-injecting the Authorization header | | BasicAuthCredentials | Type { username: string; password: string } |

Errors

| Class | Description | |---|---| | HttpRequestError | Base request error (extends Error, not HttpException) | | HttpTimeoutError | Request timeout | | HttpNetworkError | Network error |

Types

| Type | Description | |---|---| | HttpClientOptions | Options for native HttpClient | | HttpModuleOptions | Options for NestJS HttpModule | | HttpLoggingOptions | { all?: boolean; errors?: boolean; sanitizeHeaders?: string[] } | | HttpRetryOptions | Retry: maxRetries, retryDelay, retryableStatuses, exponentialBackoff | | HttpRequestConfig | Configuration for an individual request (supports per-request timeout and retry) | | HttpResponse<T> | Server response | | HttpStreamResponse | Streaming response with ReadableStream<Uint8Array> | | HttpInterceptor | Interceptor interface | | HttpLogger | Custom logger interface | | HttpAuthProvider | Auth provider interface (getAuthHeader()) | | BasicAuthCredentials | { username: string; password: string } |


Default Values

| Parameter | Value | |---|---| | timeout | 30000 ms | | retry.maxRetries | 0 (retry disabled) | | retry.retryDelay | 1000 ms | | retry.retryableStatuses | [408, 429, 500, 502, 503, 504] | | retry.exponentialBackoff | false | | logging.all | false | | logging.errors | true | | logging.sanitizeHeaders | [] (no masking) | | stream() timeout | 0 (no timeout) |

Requirements

  • Node.js ≥ 18 (native fetch)
  • NestJS ≥ 10 (for the NestJS module, optional dependency)

Type Definitions Included

This package includes:

  • *.d.ts files for all exported classes and functions
  • Type definitions for all parameters and return values
  • IntelliSense support in VS Code and other TypeScript-aware editors
  • Declaration maps for better debugging experience