| name | http-client |
| description | HTTP client patterns — fetch wrapper, axios interceptors, retry logic, request cancellation, and timeout handling. |
| layer | utility |
| category | backend |
| triggers | ["http client","fetch wrapper","axios","ky","got","request interceptor","retry logic"] |
| inputs | ["API consumption requirements","Retry and error handling strategies","Request/response interceptor patterns","Timeout and cancellation needs"] |
| outputs | ["Type-safe HTTP client wrappers","Retry logic with exponential backoff","Request/response interceptor chains","Cancellation and timeout patterns"] |
| linksTo | ["error-handling","api-caching","api-error-handling"] |
| linkedFrom | [] |
| riskLevel | low |
| memoryReadPolicy | selective |
| memoryWritePolicy | none |
| sideEffects | [] |
HTTP Client Patterns
Purpose
Provide expert guidance on building robust HTTP clients with proper error handling, retry logic, request cancellation, interceptors, and type safety. Covers native fetch, axios, ky, and got patterns for both browser and Node.js.
Key Patterns
Type-Safe Fetch Wrapper
Minimal, production-ready fetch client:
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
interface RequestConfig extends Omit<RequestInit, "method" | "body"> {
params?: Record<string, string | number | boolean | undefined>;
timeout?: number;
retries?: number;
}
interface ApiError extends Error {
status: number;
statusText: string;
data: unknown;
}
class HttpClient {
private baseUrl: string;
private defaultHeaders: Record<string, string>;
private interceptors: {
request: < | <>>;
: < | <>>;
};
() {
. = baseUrl.(, );
. = {
: ,
...defaultHeaders,
};
. = { : [], : [] };
}
() {
...(fn);
;
}
() {
...(fn);
;
}
request<T>(: , : , ?: , : = {}): <T> {
{ params, timeout = , retries = , ...fetchOptions } = config;
url = ();
(params) {
.(params).( {
(value !== ) url..(key, (value));
});
}
: = {
method,
: { ...., ...fetchOptions. <, > },
...fetchOptions,
};
(body !== && method !== ) {
requestInit. = .(body);
}
( interceptor ..) {
requestInit = (requestInit);
}
.<T>(url, requestInit, timeout, retries);
}
executeWithRetry<T>(
: ,
: ,
: ,
: ,
attempt =
): <T> {
controller = ();
timeoutId = ( controller.(), timeout);
{
response = (url, {
...init,
: controller.,
});
( interceptor ..) {
response = (response);
}
(!response.) {
data = response.().( );
error = () ;
error. = response.;
error. = response.;
error. = data;
(attempt < retries && (response. >= || response. === )) {
delay = .(attempt, response);
.(delay);
.<T>(url, init, timeout, retries, attempt + );
}
error;
}
(response. === ) T;
response.() <T>;
} (error) {
(error && error. === ) {
();
}
(attempt < retries && !(error ).) {
delay = .(attempt);
.(delay);
.<T>(url, init, timeout, retries, attempt + );
}
error;
} {
(timeoutId);
}
}
(: , ?: ): {
retryAfter = response?..();
(retryAfter) {
seconds = (retryAfter, );
(!(seconds)) seconds * ;
}
base = .( * ** attempt, );
base + .() * ;
}
(: ): <> {
( (resolve, ms));
}
get<T>(: , ?: ) { .<T>(, path, , config); }
post<T>(: , ?: , ?: ) { .<T>(, path, body, config); }
put<T>(: , ?: , ?: ) { .<T>(, path, body, config); }
patch<T>(: , ?: , ?: ) { .<T>(, path, body, config); }
<T>(: , ?: ) { .<T>(, path, , config); }
}