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

@backendkit-labs/http-client

v0.2.1

Published

Production-grade HTTP client for Node.js — built on axios with circuit breaker, retry with backoff, request cancellation, typed Result responses, pipeline middleware, and optional NestJS integration

Readme

@backendkit-labs/http-client

npm version CI License Node Docs

Production-grade HTTP client for Node.js — built on axios with circuit breaker, retry with exponential backoff, typed Result<T, E> responses, request cancellation, pre-request pipeline middleware, and optional NestJS DI integration.

Every method returns Result<HttpResponse<T>, HttpClientError> — no try/catch needed, no unhandled rejections, always typed.


Installation

npm install @backendkit-labs/http-client axios

NestJS peer dependencies (only for the /nestjs subpath):

npm install @nestjs/common @nestjs/core rxjs

TypeScript Configuration

Subpath exports (/nestjs)

This package uses the exports field in package.json to expose the /nestjs subpath. TypeScript's ability to resolve it depends on the moduleResolution setting in your tsconfig.json.

Modern resolution (recommended) — no extra config needed:

{
  "compilerOptions": {
    "moduleResolution": "bundler"
  }
}

"bundler", "node16", and "nodenext" all understand the exports field natively.

Legacy resolution ("node") — add paths aliases:

{
  "compilerOptions": {
    "moduleResolution": "node",
    "paths": {
      "@backendkit-labs/http-client/nestjs": [
        "./node_modules/@backendkit-labs/http-client/dist/nestjs/index.d.ts"
      ]
    }
  }
}

NestJS decorator support:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

And in your main.ts, before anything else:

import 'reflect-metadata';

Quick Start

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

const client = new HttpClient({
  baseURL: 'https://api.example.com',
  timeout: 5_000,
});

// All methods return Result<HttpResponse<T>, HttpClientError>
const result = await client.get<User[]>('/users');

if (result.ok) {
  console.log(result.value.data);   // User[]
  console.log(result.value.status); // 200
} else {
  console.error(result.error.type);   // 'http' | 'network' | 'timeout' | 'cancelled' | 'circuit-open'
  console.error(result.error.status); // 404, 500, etc. (for 'http' type)
}

Configuration

const client = new HttpClient({
  baseURL:  'https://api.example.com',
  timeout:  10_000,          // default: 10 000 ms
  headers:  { 'X-API-Key': 'secret' },

  // Retry with exponential backoff + jitter
  retry: {
    attempts:    3,           // retries after first failure
    delayMs:     100,
    maxDelayMs:  5_000,
    jitter:      true,
    shouldRetry: (err) => err.type === 'network' || err.type === 'timeout',
  },

  // Circuit breaker
  circuitBreaker: {
    failureThreshold:  50,    // % of calls that must fail to open the circuit
    minimumCalls:      5,     // minimum calls before evaluating thresholds
    slidingWindowSize: 10,
    openTimeoutMs:     30_000,
  },

  // Pre-request middleware steps
  steps: [authStep, correlationIdStep],
});

HTTP Methods

All methods accept an optional RequestConfig:

interface RequestConfig {
  headers?:       Record<string, string>;
  params?:        Record<string, unknown>;   // query string parameters
  timeout?:       number;                    // per-request override
  cancelKey?:     string;                    // key to cancel this request
  correlationId?: string;
}
client.get<T>(url, config?)
client.post<T>(url, data?, config?)
client.put<T>(url, data?, config?)
client.patch<T>(url, data?, config?)
client.delete<T>(url, config?)

Error Types

type HttpErrorType = 'http' | 'network' | 'timeout' | 'cancelled' | 'circuit-open';

interface HttpClientError {
  type:     HttpErrorType;
  message:  string;
  status?:  number;   // only for 'http'
  data?:    unknown;  // response body, only for 'http'
  cause?:   unknown;  // original axios error
}

Request Cancellation

Register a cancelKey on the request and cancel by key at any time:

const promise = client.get('/long-poll', { cancelKey: 'my-poll' });

// Cancel a specific request
client.cancelRequest('my-poll');

// Cancel all in-flight requests
client.cancelAll();

const result = await promise;
if (!result.ok && result.error.type === 'cancelled') {
  // handle cancellation
}

Pipeline Middleware

Pre-request middleware steps transform the HttpCtx before each request. Steps are powered by @backendkit-labs/pipeline.

import type { PipelineStep, StepResult } from '@backendkit-labs/pipeline';
import { Ok } from '@backendkit-labs/pipeline';
import type { HttpCtx, HttpClientError } from '@backendkit-labs/http-client';

const authStep: PipelineStep<HttpCtx, HttpClientError> = {
  stepName: 'auth',
  async handle(ctx): Promise<StepResult<HttpCtx, HttpClientError>> {
    const token = await tokenStore.get();
    return Ok({ ...ctx, headers: { ...ctx.headers, Authorization: `Bearer ${token}` } });
  },
};

const client = new HttpClient({ steps: [authStep] });

A step can abort the request by returning Err(...):

import { Err } from '@backendkit-labs/pipeline';

const rateLimitStep: PipelineStep<HttpCtx, HttpClientError> = {
  stepName: 'rate-limit',
  async handle(ctx): Promise<StepResult<HttpCtx, HttpClientError>> {
    if (await rateLimiter.isExceeded()) {
      return Err({ type: 'network', message: 'Rate limit exceeded' });
    }
    return Ok(ctx);
  },
};

Observability

// Snapshot of lifetime counters
client.getMetrics();
// → { requests, success, failed, cancelled, circuitOpen, retried }

// Circuit breaker state and counters
client.getCircuitBreakerState();   // 'closed' | 'open' | 'half_open' | undefined
client.getCircuitBreakerMetrics(); // detailed metrics or undefined

NestJS Integration

Module registration

// Define typed injection tokens
export const PRIMARY_API   = defineHttpClient('primary-api');
export const PAYMENTS_API  = defineHttpClient('payments-api');
import { HttpClientModule } from '@backendkit-labs/http-client/nestjs';
import { PRIMARY_API, PAYMENTS_API } from './tokens';

@Module({
  imports: [
    HttpClientModule.forRoot({
      clients: [
        { token: PRIMARY_API,  config: { baseURL: 'https://api.example.com',     retry: { attempts: 3, delayMs: 100 } } },
        { token: PAYMENTS_API, config: { baseURL: 'https://payments.example.com', circuitBreaker: { failureThreshold: 40, minimumCalls: 3 } } },
      ],
    }),
  ],
})
export class AppModule {}

Injection

import { InjectHttpClient } from '@backendkit-labs/http-client/nestjs';
import { PRIMARY_API } from './tokens';

@Injectable()
export class UserService {
  constructor(
    @InjectHttpClient(PRIMARY_API) private readonly http: HttpClient,
  ) {}

  async getUsers(): Promise<User[]> {
    const result = await this.http.get<User[]>('/users');
    if (!result.ok) throw new Error(result.error.message);
    return result.value.data;
  }
}

Async module registration

HttpClientModule.forRootAsync({
  imports: [ConfigModule],
  inject:  [ConfigService],
  useFactory: (config: ConfigService) => ({
    clients: [{
      token:  PRIMARY_API,
      config: { baseURL: config.get('API_URL'), timeout: config.get('API_TIMEOUT') },
    }],
  }),
}),

Named Clients

import { defineHttpClient, HttpClientToken } from '@backendkit-labs/http-client';

export const GITHUB_API: HttpClientToken = defineHttpClient('github-api');

// Provides the token's symbol as the NestJS DI token:
// Inject with @InjectHttpClient(GITHUB_API)

License

Apache-2.0