Interceptors & middleware
Request lifecycle (short)
- Merge defaults + call config.
- Run request interceptors on the config.
- Build
OpenFetchContext(url,request,response,error). - Run middleware stack; innermost calls
dispatch(fetch+ parse + validate + response transforms). - Run response interceptors on the successful response.
- Return full response or
dataifunwrapResponse.
See Architecture & internals for diagrams, or the repo’s openFetch/docs/PROJECT_FLOW.md for a file map.
Inside dispatch (after next() reaches core)
Order matters for debugging transforms and errors:
transformRequest— each function receives(data, headers); may change body and headers beforefetch.fetch— single call with mergedRequestInit+ computed URL/body.- Body parse — unless
rawResponseis true: JSON/text/blob/etc. perresponseTypeorContent-Type. validateStatus— failure throwsOpenFetchError(ERR_BAD_RESPONSE) with response attached.transformResponse— skipped whenrawResponseis true.
Response interceptors run on the resulting OpenFetchResponse (so for rawResponse, data is still the native Response).
Interceptors
Each client exposes:
client.interceptors.request.use(fulfilled?, rejected?);
client.interceptors.response.use(fulfilled?, rejected?);Order:
- Request: handlers run last registered first (LIFO).
- Response: handlers run first registered first (FIFO).
Handlers return the value or a Promise. rejected follows standard promise chaining semantics.
Middleware
Type:
type Middleware = (
ctx: OpenFetchContext,
next: NextFn
) => Promise<void>;ctx.request— Final merged config for this round trip.ctx.response— Set bydispatch(or a middleware) on success.ctx.error— May be set when something throws inside the stack. The client inclient.tsprefers a successfulctx.response: ifctx.erroris set butctx.responseis non-null (for example after retry recovered), the client does not throwctx.error. If there is still no response after middleware finishes, the error is propagated.
Who calls dispatch?
Only the innermost middleware handler passed to applyMiddlewares invokes dispatch. Retry middleware calls next() again so everything below retry in the stack runs once per attempt; middleware above retry wraps the whole loop.
Register on the client:
client.use(async (ctx, next) => {
console.log("before", ctx.request.method);
await next();
console.log("after", ctx.response?.status);
});client.use pushes onto defaults.middlewares.
Ordering
Outer middleware runs first when entering the stack. Order matters for cache vs retry: e.g. cache before retry avoids retrying on cache hits; retry before cache retries origin failures before a cache layer sees them. Choose based on product rules.
Built-in middleware factories
createRetryMiddleware(options?)— See Retry & cache.createCacheMiddleware(store, options?)— See Retry & cache.
Convenience wrappers (same middleware, nicer DX): retry, timeout, hooks, debug, strictFetch from @hamdymohamedak/openfetch/plugins — Plugins & fluent API.
