http-client
v0.2.0@backendkit-labs/http-client
Every HTTP call returns Result<T, E> — no try/catch anywhere.
Overview#
Making HTTP calls is one of the most common and most failure-prone backend tasks. Popular libraries like Axios and Got treat failures as exceptions — forcing every caller to wrap calls with try/catch, manage retries manually, and wire up circuit breakers separately.
@backendkit-labs/http-client is built on Axios for ecosystem compatibility but integrates the full BackendKit pattern stack natively:
- →Every response is Result<T, HttpClientError> — no try/catch anywhere.
- →Built-in exponential backoff + jitter retry for transient errors.
- →Integrated circuit breaker using the same isFailure semantics as @backendkit-labs/circuit-breaker.
- →Cancellation by key (for polling) and mass cancellation when shutting down.
- →Middleware as pipeline steps: auth headers, logging, rate limiting — all composable and testable.
- →defineHttpClient<T>() creates fully typed client contracts injectable via NestJS DI.
Configuration#
npm install @backendkit-labs/http-client axios| Prop | Type | Description |
|---|---|---|
| baseUrl | string | Base URL prepended to all requests. |
| timeout | number | Request timeout in milliseconds. |
| defaultHeaders | Record<string, string> | Headers included on every request. |
| retry.attempts | number | Number of retry attempts after the initial failure. |
| retry.baseDelay | number | Initial delay in ms. Doubles on each retry (exponential backoff + jitter). |
| retry.retryOn | string[] | Error types to retry: 'network_error' | 'timeout'. |
| circuitBreaker | CircuitBreakerConfig | Inline circuit breaker — same isFailure semantics as @backendkit-labs/circuit-breaker. |
Making Requests#
| Prop | Type | Description |
|---|---|---|
| get<T>(path, cfg?) | Promise<Result<T, E>> | GET request. |
| post<T>(path, body, cfg?) | Promise<Result<T, E>> | POST with JSON body. |
| put<T>(path, body, cfg?) | Promise<Result<T, E>> | PUT with JSON body. |
| patch<T>(path, body, cfg?) | Promise<Result<T, E>> | PATCH with JSON body. |
| delete<T>(path, cfg?) | Promise<Result<T, E>> | DELETE request. |
| cancelByKey(key) | void | Cancel all in-flight requests with the given key. Useful for polling. |
| cancelAll() | void | Cancel every in-flight request — use on shutdown. |
Error Types#
| Prop | Type | Description |
|---|---|---|
| timeout | HttpClientError | Request exceeded the configured timeout. |
| network_error | HttpClientError | Connection refused, DNS failure, or network unreachable. |
| http_error | HttpClientError | Server responded with 4xx or 5xx. Includes status and response body. |
| circuit_open | HttpClientError | Integrated circuit breaker is open — request was not sent. |
| cancelled | HttpClientError | Request was cancelled via cancelByKey() or cancelAll(). |
Retry & Circuit Breaker#
Retry uses exponential backoff with jitter to prevent thundering herd: each attempt waits baseDelay × 2ⁿ + random ms. The circuit breaker wraps the full retry cycle — if the breaker opens mid-retry, the attempt fails immediately without exhausting remaining retries.
Typed NestJS clients
defineHttpClient<T>() creates a strongly-typed DI token. Register instances once in the module; inject by token anywhere.
import { HttpClientModule, defineHttpClient } from '@backendkit-labs/http-client/nestjs'; export const STRIPE_CLIENT = defineHttpClient<StripeClient>(); @Module({ imports: [ HttpClientModule.forRoot({ clients: [ { token: STRIPE_CLIENT, config: { baseUrl: 'https://api.stripe.com/v1', timeout: 10_000, retry: { attempts: 3, baseDelay: 500 }, circuitBreaker: { name: 'stripe', failureThreshold: 40 }, }, }, ], }), ], }) export class PaymentModule {} @Injectable() export class PaymentService { constructor( @InjectHttpClient(STRIPE_CLIENT) private readonly stripe: StripeClient, ) {} }
Examples#
From basic to production-grade — copy and adapt.
import { HttpClient } from '@backendkit-labs/http-client'; const github = new HttpClient({ baseUrl: 'https://api.github.com', timeout: 8_000, defaultHeaders: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, Accept: 'application/vnd.github.v3+json', }, }); const result = await github.get<Repository>('/repos/owner/repo'); if (result.ok) { return result.value.stargazers_count; // typed Repository } // result.error.type: 'timeout' | 'network_error' | 'http_error'
⚖️ vs. Alternatives#
Comparing against axios and got — the two most popular HTTP clients in the Node.js ecosystem.
| Feature | @backendkit-labs/http-client | axios | got |
|---|---|---|---|
| Result<T,E> responses | ✅ Always | ❌ Throws AxiosError | ❌ Throws |
| Built-in retry | ✅ Exponential backoff | ❌ Manual / plugin | ✅ Built-in |
| Circuit breaker | ✅ Integrated | ❌ | ❌ |
| Typed error variants | ✅ timeout/network/http_error | ⚠️ Generic AxiosError | ⚠️ Partial |
| No try/catch required | ✅ | ❌ | ❌ |
| NestJS integration | ✅ @InjectHttpClient | ⚠️ @nestjs/axios | ❌ |
| Named instances (DI) | ✅ | ⚠️ Manual | ❌ |
| Runtime dependencies | axios (peer) | 0 (is the dep) | 0 |
| Weekly downloads | Growing | ~50M | ~2M |
✅ Supported · ❌ Not supported · ⚠️ Partial / workaround needed. Download counts are approximate weekly npm averages.