Http
HTTP client with timeout handling, exponential backoff retry logic, and errortransformation.
HTTP Client Adapter (adapters/http/)
HTTP client with timeout handling, exponential backoff retry logic, and errortransformation.
Implementation
fetch-client.ts
Implements HttpClientPort using native Fetch API with enhancements.
Features:
- Timeout Control: Configurable per-request timeouts with AbortController
- Exponential Backoff: Automatic retries with increasing delays
- Error Transformation: Converts fetch errors to domain errors
- JSON Handling: Automatic content-type detection and parsing
- Logging: Detailed request/retry logging for debugging
API
interface HttpClientPort {
request<T>(
url: string,
options: HttpRequestOptions,
): Promise<HttpResponse<T>>;
get<T>(url: string, options?: HttpRequestOptions): Promise<HttpResponse<T>>;
post<T>(
url: string,
body: unknown,
options?: HttpRequestOptions,
): Promise<HttpResponse<T>>;
}
Request Options
interface HttpRequestOptions {
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
headers?: Record<string, string>;
body?: unknown;
timeoutMs?: number; // Default: 10000ms
maxRetries?: number; // Default: 3
retryDelayMs?: number; // Default: 1000ms
retryBackoffMultiplier?: number; // Default: 2
}
Response Structure
interface HttpResponse<T> {
status: number;
data: T;
headers: Record<string, string>;
fromCache: boolean;
}
Retry Logic
Exponential Backoff
Attempt 1: Immediate
Attempt 2: Wait 1000ms
Attempt 3: Wait 2000ms (1000 × 2)
Attempt 4: Wait 4000ms (2000 × 2)
Retry Conditions
✅ Retries:
- Network errors
- Timeout errors (AbortError)
❌ No Retry:
- Invalid JSON responses (data validation errors)
- Malformed API responses
Error Handling
Timeout
// After timeoutMs, request is aborted
throw new ApiTimeoutError({
url: "https://api.example.com/data",
timeoutMs: 10000,
originalError: "AbortError",
});
Invalid Response
// Non-JSON or malformed response
throw new InvalidApiResponseError("Response is not valid JSON", {
url,
contentType: "text/html",
bodyPreview: "<!DOCTYPE html>...",
});
Network Error
// Unknown errors wrapped
throw new InvalidApiResponseError("HTTP request failed", {
url,
originalError: "Network request failed",
});
Configuration
Default configuration from lib/config.ts:
njApi: {
timeoutMs: 10000,
retryEnabled: true,
maxRetries: 3,
retryDelayMs: 1000,
retryBackoffMultiplier: 2
}
Usage Example
import { createFetchClient } from "./adapters/http/fetch-client";
const client = createFetchClient(logger);
// GET with timeout
const response = await client.get<ApiResponse>(url, {
timeoutMs: 5000,
maxRetries: 2,
});
// POST with body
const result = await client.post<CreateResponse>(url, {
name: "example",
value: 42,
});
Testing
- Unit tests: Mocked fetch with various scenarios (success, timeout, retry, errors)
- Integration tests: Real HTTP calls to public APIs (httpbin.org)
- Timeout tests: Verify AbortController behavior
- Retry tests: Verify exponential backoff timing
- Error tests: Verify domain error transformations
Performance
- Concurrent Requests: No built-in concurrency limits (manage at caller level)
- Memory: Minimal overhead (~1KB per request)
- Latency: Native fetch performance + retry delays