smart-fetch-client
v1.0.1
Published
A lightweight, fully typed fetch wrapper with retries, timeouts, interceptors, and normalized errors.
Maintainers
Readme
Smart Fetch Client
A production-ready, dependency-free TypeScript wrapper around native fetch that improves developer experience and reliability without sacrificing bundle size.
It adds practical API client features such as base URL support, interceptors, retries with exponential backoff, timeout handling, auth token injection, request cancellation, query params, and normalized errors.
Installation
npm install smart-fetch-clientQuick Start
import { createClient } from "smart-fetch-client";
interface User {
id: string;
name: string;
}
const api = createClient({
baseURL: "https://api.example.com",
timeout: 5000,
retries: 3,
headers: {
"Content-Type": "application/json",
},
getToken: () => localStorage.getItem("token"),
onRequest: (config) => config,
onResponse: (response) => response,
onError: (error) => {
console.error(error.message);
},
});
const users = await api.get<User[]>("/users");
await api.post("/users", {
name: "Sina",
});API
Create Client
const api = createClient({
baseURL: "https://api.example.com",
timeout: 5000,
retries: 3,
retryDelayMs: 300,
headers: {
"Content-Type": "application/json",
},
getToken: () => "my-token",
onRequest: (config) => config,
onResponse: (response, context) => response,
onError: (error, context) => {
console.error(context.attempt, error.message);
},
});Request Methods
api.get<T>(url, options?);
api.post<T>(url, body, options?);
api.put<T>(url, body, options?);
api.patch<T>(url, body, options?);
api.delete<T>(url, options?);
api.request<T>(method, url, options?);Features
Base URL Handling
baseURL is automatically prepended to relative URLs.
await api.get("/users"); // -> https://api.example.com/usersQuery Parameters
Pass query params through options.params.
await api.get("/users", {
params: {
page: 1,
limit: 10,
tags: ["admin", "active"],
},
});Timeout Support
Requests are cancelled with AbortController when timeout is exceeded.
await api.get("/reports", { timeout: 2000 });Retry with Exponential Backoff
Retries are applied for:
- Network failures
5xxresponses
Backoff progression uses: retryDelayMs * 2^attempt.
const api = createClient({ retries: 3, retryDelayMs: 250 });Interceptors
Interceptors let you hook into request/response/error flows.
const api = createClient({
onRequest: (config) => {
config.headers.set("x-trace-id", crypto.randomUUID());
return config;
},
onResponse: async (response) => {
if (response.status === 204) {
return new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
return response;
},
onError: (error, context) => {
console.warn(`Attempt ${context.attempt + 1} failed:`, error.message);
},
});Auth Token Injection
When getToken is provided, Authorization: Bearer <token> is added automatically.
const api = createClient({
getToken: () => sessionStorage.getItem("access_token"),
});Error Normalization
All thrown errors use ApiError.
import { ApiError } from "smart-fetch-client";
try {
await api.get("/protected");
} catch (error) {
if (ApiError.isApiError(error)) {
console.error(error.status); // optional HTTP status
console.error(error.data); // parsed server error body, if available
}
}Response Parsing
Response parsing is automatic and based on Content-Type:
- JSON (
application/json,+json) - Text (
text/*) - Blob (fallback)
Request Cancellation
Use native AbortSignal.
const controller = new AbortController();
const pending = api.get("/users", { signal: controller.signal });
controller.abort();
await pending;Advanced Usage
Per-request Overrides
Each request can override default timeout, retries, headers, params, and cancellation signal.
await api.post(
"/sync",
{ force: true },
{
timeout: 15_000,
retries: 1,
headers: { "x-priority": "high" },
},
);Error-aware Retry Strategies
Use onError to log or integrate custom telemetry for retries and failures.
const api = createClient({
retries: 3,
onError: (error, { attempt, maxRetries, url }) => {
console.log(`[${attempt + 1}/${maxRetries + 1}] ${url}: ${error.message}`);
},
});Build
npm run buildBuild output is generated into dist/ with:
- ESM (
dist/index.js) - CommonJS (
dist/index.cjs) - Type declarations (
dist/index.d.ts)
License
MIT
