Key features
- Timeouts and retries with jitter
- Abort-aware retry cancellation
- Per-request overrides and hooks
- Optional plugins: dedupe, circuit breaker, hedge, bulkhead, context-id
- Optional request/response shortcuts and download progress
@fetchkit/ffetch
ffetch is a TypeScript-first fetch wrapper with production defaults: timeouts, retries with exponential backoff and jitter, structured errors, and optional plugins for advanced resilience behavior.
Plain fetch gives you a promise. ffetch gives you a client. The difference is everything that has to happen around the request: aborting if the server goes silent, retrying with backoff when it fails transiently, surfacing structured errors instead of raw responses, and optionally deduplicating identical in-flight requests or tripping a circuit breaker when an upstream degrades.
Timeouts & retries
Every request gets an abort timeout and configurable retry count with exponential backoff and jitter out of the box.
Structured errors
Network failures, HTTP errors, timeouts, and circuit trips are distinct typed classes - not plain Error objects to parse.
Opt-in plugins
Dedupe, circuit breaker, bulkhead, hedge, and shortcut plugins are tree-shakeable - import only what you need.
import { createClient } from '@fetchkit/ffetch'
const api = createClient({ timeout: 5_000, retries: 2 })
const response = await api('https://api.example.com/users')
const users = await response.json()
Plugins are tree-shakeable and opt-in. Add dedupe to collapse identical in-flight requests, circuit breaker to stop hammering failing upstreams, and the shortcut plugins for a fluent request/response API.
import { createClient } from '@fetchkit/ffetch'
import { dedupePlugin } from '@fetchkit/ffetch/plugins/dedupe'
import { circuitPlugin } from '@fetchkit/ffetch/plugins/circuit'
import { requestShortcutsPlugin } from '@fetchkit/ffetch/plugins/request-shortcuts'
import { responseShortcutsPlugin } from '@fetchkit/ffetch/plugins/response-shortcuts'
const api = createClient({
timeout: 10_000,
retries: 2,
plugins: [
dedupePlugin({ ttl: 30_000, sweepInterval: 5_000 }),
circuitPlugin({ threshold: 5, reset: 30_000 }),
responseShortcutsPlugin(),
requestShortcutsPlugin(),
],
})
const users = await api
.get('https://api.example.com/users')
.json()
ffetch throws distinct typed errors - catch what you can handle, let the rest propagate.
import { createClient, TimeoutError, CircuitOpenError, HttpError, RetryLimitError } from '@fetchkit/ffetch'
try {
const data = await api.get('https://api.example.com/users').json()
} catch (e) {
if (e instanceof TimeoutError) {
// server went silent - show a retry prompt
} else if (e instanceof CircuitOpenError) {
// upstream is degraded - fail fast, return cached/fallback data
} else if (e instanceof RetryLimitError) {
// exhausted retries - log and surface error to user
} else if (e instanceof HttpError) {
// non-2xx after retries - e.g. 404, 401
} else {
throw e // unexpected - let it bubble
}
}