REST Client
createRestClient():
createRestClient(config: HttpConfig): RestClientCreates a full-featured HTTP client built on top of axios: retry, response caching, rate limiting, circuit breaker, auth provider, request tracing, idempotency keys, and an offline queue — all opt-in via HttpConfig.
Methods
get(url, config?)
GET request.
post(url, data?, config?)
POST request.
put(url, data?, config?)
PUT request.
patch(url, data?, config?)
PATCH request.
delete(url, config?)
DELETE request.
request(url, config?)
Generic request.
cancellableRequest(key, url, config?)
Request cancellable by key.
cancelRequest(key)
Cancel a request previously made with cancellableRequest(key, ...).
clearCache()
Clear this client's entire response cache. async.
invalidateCache(matcher)
Clear only cache entries whose URL matches matcher (substring, RegExp, or (info) => boolean). Returns Promise<number> — the number of entries removed. See Targeted cache invalidation below.
getCircuitBreakerState()
Promise<"closed" | "open" | "half-open" | null> — null if circuitBreaker isn't configured. Resolves synchronously (no real async work) unless circuitBreaker.store is set.
getQueuedRequests()
Promise<QueuedRequest[]> — requests awaiting the next offline-queue flush (empty if offlineQueue isn't configured).
flushQueue()
Manually attempt to send everything queued (also happens automatically on reconnect). No-op if offlineQueue isn't configured.
Options
HttpConfig — every field is optional unless noted.
baseURL
string
Base URL for all requests.
timeout
number
Request timeout in ms.
headers
Record<string, string>
Default headers sent with every request.
withCredentials
boolean
Include cookies on cross-origin requests.
retry
RetryConfig
Retry with exponential backoff. See RequestExecutor — the class that actually implements retry for both createRestClient() and standalone use.
| Field | Description |
|---|---|
attempts | Number of retry attempts |
delayMs | Base delay between retries in ms |
backoffMultiplier | Exponential backoff multiplier |
retriableStatus | HTTP status codes eligible for retry (e.g. [429, 500, 503]) |
maxRetryAfterMs | Max wait from Retry-After header in ms (default: 60000) |
jitterStrategy | Backoff jitter algorithm — see Jitter strategies |
cache
CacheConfig
Response caching for GET requests.
| Field | Description |
|---|---|
enabled | Enable response caching |
ttlMs | Cache TTL in ms |
strategy | "strict" (default) or "stale-while-revalidate" |
staleMs | Extra time after ttlMs a stale response may still be served (SWR strategy) |
store | Custom CacheStore backend — see Custom cache backend below |
rateLimit
RateLimitConfig
Concurrency + requests-per-interval limiting.
| Field | Description |
|---|---|
maxConcurrent | Max simultaneous requests |
maxRequestsPerInterval | Max requests per time window |
intervalMs | Time window size in ms |
store | Custom RateLimiterStore backend — see Distributed rate limiting |
key | Bucket name when using a shared store (default: random per-instance id — without an explicit key, a store has no sharing effect) |
leaseMs | Auto-expiry (ms) for a store-backed concurrency slot if its holder crashes without releasing (default: 30000) |
onRateLimitHeaders | Callback with every response's raw headers — see Proactive throttling |
metrics
MetricsConfig
| Field | Description |
|---|---|
onRequestStart | Callback on request start |
onRequestEnd | Callback on request end (includes duration and bytes) |
auth
AuthConfig
Bearer-token injection with automatic 401 refresh. See Auth Provider for the full behavior and an example.
| Field | Description |
|---|---|
getToken | Async function returning a Bearer token (called before every request, unless tokenTtlMs is set) |
onUnauthorized | Optional async callback on 401 — refresh the token here; request is retried once |
tokenTtlMs | Cache getToken()'s result for this many ms instead of calling it before every request; invalidated automatically on 401 |
sanitizeHeaders
boolean · default: true
Mask sensitive headers in metrics callbacks (secure by default). See Log Sanitization.
sensitiveHeaders
string[]
Additional headers to mask, extending DEFAULT_SENSITIVE_HEADERS.
adapter
HttpAdapter
Custom HTTP adapter (e.g. native fetch) — replaces the built-in axios transport. See HTTP Adapter.
circuitBreaker
CircuitBreakerConfig
See Circuit breaker.
| Field | Description |
|---|---|
failureThreshold | Consecutive failures before the circuit opens |
openMs | How long the circuit stays open before probing again |
successThreshold | Successful probes needed to fully close (optional) |
isFailure | Predicate deciding whether an error counts as a failure (optional) |
store | Custom CircuitBreakerStore for shared state across instances (optional) |
key | Shared bucket name when using store (optional) |
tracing
TracingConfig
See Request tracing.
| Field | Description |
|---|---|
generateTraceparent | Add a W3C traceparent header to every request (default: false) |
provider | TracingProvider hook creating a span per request |
idempotencyHeaderName
string · default: "Idempotency-Key"
Header name used for RestRequestConfig.idempotencyKey. See Idempotency keys.
autoIdempotencyKey
boolean · default: false
Have RequestExecutor auto-generate an idempotency key per logical request. See Idempotency keys.
offlineQueue
OfflineQueueConfig
See Offline queue.
| Field | Description |
|---|---|
enabled | Turn on the offline queue |
persistAdapter | Save/load pair for queued requests |
isOnline | Function reporting current connectivity (default: navigator.onLine) |
onOnlineChange | Subscribe to connectivity changes (default: the browser's "online" event) |
shouldQueue | Which requests get queued (default: mutating methods — POST/PUT/PATCH/DELETE) |
maxQueueSize | Cap on queued requests |
onFlushSuccess | Callback when a queued request is successfully replayed |
onFlushError | Callback when a queued request fails permanently |
Per-request cache override
const res = await client.get('/data', {
useCache: true,
cacheTtlMs: 30000,
cacheKey: 'my-custom-key',
})File uploads & progress
RestRequestConfig extends axios's own AxiosRequestConfig, so data can be a FormData/Blob/ArrayBuffer and onUploadProgress/onDownloadProgress are already typed and wired through — no extra configuration needed on the default (axios) transport:
const formData = new FormData()
formData.append('file', fileInput.files[0])
await client.post('/upload', formData, {
onUploadProgress: (event) => {
const percent = event.total ? Math.round((event.loaded / event.total) * 100) : 0
console.log(`Uploaded ${percent}%`)
},
})
await client.get('/large-report.csv', {
responseType: 'blob',
onDownloadProgress: (event) => console.log(event.loaded, 'bytes received'),
})If you use a custom adapter (see HTTP Adapter) instead of the built-in axios transport, onUploadProgress/onDownloadProgress are still passed through to your adapter's config object as-is, but the adapter is responsible for actually calling them — fetch has no native upload-progress event, so a fetch-based adapter needs a ReadableStream reader (or XMLHttpRequest) to implement it. See examples/file-upload.ts.
Targeted cache invalidation
clearCache() wipes the entire response cache. To invalidate only the entries affected by a mutation (e.g. after a POST/PUT/DELETE), use invalidateCache() instead — it accepts a substring, a RegExp, or a predicate over { method, url }, and resolves to how many entries were removed. Both methods are async (so a custom cache.store can be backed by a real network call):
await client.post('/users/1/orders', newOrder)
await client.invalidateCache('/users/1') // substring match on the cached URL
await client.invalidateCache(/^https:\/\/api\.example\.com\/users\/\d+$/)
await client.invalidateCache(({ method, url }) => method === 'GET' && url.includes('/orders'))Custom cache backend (CacheStore)
By default, cache.enabled: true caches responses in an in-memory TtlCache scoped to that one client instance — fine for a browser SPA, but each server process has its own cold cache in a multi-instance deployment. Pass cache.store to use any backend implementing CacheStore instead — Redis, for example, so cached responses are shared across every instance:
import { createRestClient, type CacheStore, type ApiResponse } from 'rest-pipeline-js'
const redisStore: CacheStore<ApiResponse<unknown>> = {
async get(key) {
const raw = await redis.get(key)
return raw ? JSON.parse(raw) : undefined
},
async set(key, value, ttlMs) {
await redis.set(key, JSON.stringify(value), 'PX', ttlMs)
},
async delete(key) {
await redis.del(key)
},
async clear() {
await redis.flushdb()
},
// getStale/deleteWhere are optional — without them, the
// 'stale-while-revalidate' strategy and invalidateCache() gracefully
// degrade (see CacheStore's JSDoc) instead of throwing.
}
const client = createRestClient({
baseURL: 'https://api.example.com',
cache: { enabled: true, ttlMs: 60_000, store: redisStore },
})See examples/redis-cache-store.ts for the full annotated version.
Full example
import { createRestClient } from 'rest-pipeline-js'
const client = createRestClient({
baseURL: 'https://api.example.com',
timeout: 5000,
retry: {
attempts: 2,
delayMs: 500,
backoffMultiplier: 2,
retriableStatus: [429, 500, 503],
},
cache: { enabled: true, ttlMs: 60000 },
rateLimit: { maxConcurrent: 3, maxRequestsPerInterval: 10, intervalMs: 1000 },
auth: {
getToken: async () => localStorage.getItem('token') ?? '',
onUnauthorized: async () => {
/* refresh token here */
},
},
sanitizeHeaders: true,
})
const res = await client.get('/users/1')
console.log(res.data)
// PATCH support
await client.patch('/users/1', { name: 'Alice' })
// Cancellable request
const req = client.cancellableRequest('my-key', '/search', {
params: { q: 'foo' },
})
// Cancel it any time:
client.cancelRequest('my-key')