0
# Request and Response Interceptors
1
2
Lifecycle hooks for modifying requests, handling responses, implementing logging, authentication, and custom logic during the fetch process.
3
4
## Capabilities
5
6
### Interceptor System Overview
7
8
ofetch provides four lifecycle hooks that allow you to intercept and modify requests and responses at different stages of the fetch process.
9
10
```typescript { .api }
11
interface FetchHooks<T = any, R extends ResponseType = ResponseType> {
12
onRequest?: MaybeArray<FetchHook<FetchContext<T, R>>>;
13
onRequestError?: MaybeArray<FetchHook<FetchContext<T, R> & { error: Error }>>;
14
onResponse?: MaybeArray<FetchHook<FetchContext<T, R> & { response: FetchResponse<T> }>>;
15
onResponseError?: MaybeArray<FetchHook<FetchContext<T, R> & { response: FetchResponse<T> }>>;
16
}
17
18
type FetchHook<C extends FetchContext = FetchContext> = (
19
context: C
20
) => MaybePromise<void>;
21
22
type MaybePromise<T> = T | Promise<T>;
23
type MaybeArray<T> = T | T[];
24
```
25
26
### Request Interceptor
27
28
Called before the request is sent, allowing modification of options, URL, headers, and logging.
29
30
```typescript { .api }
31
/**
32
* Hook called before request is sent
33
* @param context - Contains request and options that can be modified
34
*/
35
onRequest?: MaybeArray<FetchHook<FetchContext<T, R>>>;
36
37
interface FetchContext<T = any, R extends ResponseType = ResponseType> {
38
request: FetchRequest;
39
options: ResolvedFetchOptions<R>;
40
response?: FetchResponse<T>;
41
error?: Error;
42
}
43
```
44
45
**Usage Examples:**
46
47
```typescript
48
import { ofetch } from "ofetch";
49
50
// Add authentication token
51
const api = ofetch.create({
52
baseURL: "https://api.example.com",
53
async onRequest({ request, options }) {
54
// Add auth token to all requests
55
const token = await getAuthToken();
56
options.headers.set("Authorization", `Bearer ${token}`);
57
}
58
});
59
60
// Request logging
61
const api = ofetch.create({
62
onRequest({ request, options }) {
63
console.log(`[${options.method || "GET"}] ${request}`);
64
console.log("Headers:", Object.fromEntries(options.headers));
65
}
66
});
67
68
// Add timestamp to query parameters
69
const api = ofetch.create({
70
onRequest({ request, options }) {
71
options.query = options.query || {};
72
options.query.timestamp = Date.now();
73
}
74
});
75
76
// Multiple request hooks
77
const data = await ofetch("/api/data", {
78
onRequest: [
79
({ options }) => {
80
options.headers.set("X-Client", "ofetch");
81
},
82
async ({ options }) => {
83
const userId = await getCurrentUserId();
84
options.headers.set("X-User-ID", userId);
85
}
86
]
87
});
88
```
89
90
### Request Error Interceptor
91
92
Called when the fetch request fails (network error, timeout, etc.), before retry logic is applied.
93
94
```typescript { .api }
95
/**
96
* Hook called when fetch request encounters an error
97
* @param context - Contains request, options, and error
98
*/
99
onRequestError?: MaybeArray<FetchHook<FetchContext<T, R> & { error: Error }>>;
100
```
101
102
**Usage Examples:**
103
104
```typescript
105
import { ofetch } from "ofetch";
106
107
// Log request errors
108
const api = ofetch.create({
109
onRequestError({ request, error }) {
110
console.error(`Request failed: ${request}`, error.message);
111
}
112
});
113
114
// Custom error handling
115
const api = ofetch.create({
116
onRequestError({ request, options, error }) {
117
if (error.name === "TimeoutError") {
118
console.log(`Request to ${request} timed out after ${options.timeout}ms`);
119
} else if (error.name === "AbortError") {
120
console.log(`Request to ${request} was aborted`);
121
}
122
}
123
});
124
125
// Error metrics collection
126
const api = ofetch.create({
127
onRequestError({ request, error }) {
128
metrics.increment("http.request.error", {
129
url: String(request),
130
error_type: error.name
131
});
132
}
133
});
134
```
135
136
### Response Interceptor
137
138
Called after a successful response is received and parsed, allowing response modification and logging.
139
140
```typescript { .api }
141
/**
142
* Hook called after successful response is received and parsed
143
* @param context - Contains request, options, and response with parsed data
144
*/
145
onResponse?: MaybeArray<FetchHook<FetchContext<T, R> & { response: FetchResponse<T> }>>;
146
147
interface FetchResponse<T> extends Response {
148
_data?: T;
149
}
150
```
151
152
**Usage Examples:**
153
154
```typescript
155
import { ofetch } from "ofetch";
156
157
// Response logging
158
const api = ofetch.create({
159
onResponse({ request, response }) {
160
console.log(`[${response.status}] ${request}`);
161
console.log("Response time:", response.headers.get("x-response-time"));
162
}
163
});
164
165
// Response caching
166
const cache = new Map();
167
const api = ofetch.create({
168
onResponse({ request, response }) {
169
if (response.status === 200) {
170
cache.set(String(request), response._data);
171
}
172
}
173
});
174
175
// Response transformation
176
const api = ofetch.create({
177
onResponse({ response }) {
178
// Transform all responses to include metadata
179
if (response._data && typeof response._data === "object") {
180
response._data.meta = {
181
status: response.status,
182
timestamp: Date.now()
183
};
184
}
185
}
186
});
187
188
// Response validation
189
const api = ofetch.create({
190
onResponse({ response }) {
191
if (response._data && !isValidResponse(response._data)) {
192
throw new Error("Invalid response format");
193
}
194
}
195
});
196
```
197
198
### Response Error Interceptor
199
200
Called when a response has an error status (4xx, 5xx) but fetch succeeded, before retry logic is applied.
201
202
```typescript { .api }
203
/**
204
* Hook called when response has error status (4xx, 5xx)
205
* @param context - Contains request, options, and error response
206
*/
207
onResponseError?: MaybeArray<FetchHook<FetchContext<T, R> & { response: FetchResponse<T> }>>;
208
```
209
210
**Usage Examples:**
211
212
```typescript
213
import { ofetch } from "ofetch";
214
215
// Handle authentication errors
216
const api = ofetch.create({
217
async onResponseError({ response }) {
218
if (response.status === 401) {
219
await refreshAuthToken();
220
// Note: This won't retry the request automatically
221
// You'd need to handle retry manually or let default retry logic handle it
222
}
223
}
224
});
225
226
// Log error responses
227
const api = ofetch.create({
228
onResponseError({ request, response }) {
229
console.error(`[${response.status}] ${request}:`, response._data);
230
}
231
});
232
233
// Custom error handling by status
234
const api = ofetch.create({
235
onResponseError({ response }) {
236
switch (response.status) {
237
case 400:
238
console.error("Bad request:", response._data);
239
break;
240
case 403:
241
console.error("Forbidden - check permissions");
242
break;
243
case 429:
244
console.warn("Rate limited - request will be retried");
245
break;
246
case 500:
247
console.error("Server error:", response._data);
248
break;
249
}
250
}
251
});
252
```
253
254
### Hook Chaining and Arrays
255
256
Multiple hooks can be provided as arrays and will be executed sequentially.
257
258
**Usage Examples:**
259
260
```typescript
261
import { ofetch } from "ofetch";
262
263
// Multiple hooks as array
264
const api = ofetch.create({
265
onRequest: [
266
({ options }) => {
267
options.headers.set("X-Client", "ofetch");
268
},
269
async ({ options }) => {
270
const token = await getToken();
271
options.headers.set("Authorization", `Bearer ${token}`);
272
},
273
({ request, options }) => {
274
console.log(`[${options.method || "GET"}] ${request}`);
275
}
276
],
277
onResponse: [
278
({ response }) => {
279
console.log(`Response: ${response.status}`);
280
},
281
({ response }) => {
282
metrics.recordResponseTime(response.headers.get("x-response-time"));
283
}
284
]
285
});
286
287
// Per-request hooks combined with instance hooks
288
const data = await api("/api/data", {
289
onRequest({ options }) {
290
options.headers.set("X-Custom", "per-request");
291
},
292
onResponse({ response }) {
293
console.log("Per-request response handler");
294
}
295
});
296
```
297
298
### Advanced Hook Patterns
299
300
Complex interceptor patterns for authentication, caching, and error handling.
301
302
**Usage Examples:**
303
304
```typescript
305
import { ofetch } from "ofetch";
306
307
// Automatic token refresh with retry
308
const api = ofetch.create({
309
baseURL: "https://api.example.com",
310
async onRequest({ options }) {
311
const token = await getValidToken(); // Refreshes if expired
312
options.headers.set("Authorization", `Bearer ${token}`);
313
},
314
async onResponseError({ response, request, options }) {
315
if (response.status === 401) {
316
await clearTokenCache();
317
// Let retry logic handle the retry with fresh token
318
}
319
}
320
});
321
322
// Request/response correlation
323
const correlationMap = new Map();
324
const api = ofetch.create({
325
onRequest({ request, options }) {
326
const id = Math.random().toString(36);
327
options.headers.set("X-Correlation-ID", id);
328
correlationMap.set(id, { start: Date.now(), request });
329
},
330
onResponse({ response }) {
331
const id = response.headers.get("X-Correlation-ID");
332
if (id && correlationMap.has(id)) {
333
const { start, request } = correlationMap.get(id);
334
console.log(`${request} completed in ${Date.now() - start}ms`);
335
correlationMap.delete(id);
336
}
337
}
338
});
339
340
// Conditional interceptors
341
const api = ofetch.create({
342
onRequest({ request, options }) {
343
// Only add analytics for specific endpoints
344
if (String(request).includes("/api/analytics")) {
345
options.headers.set("X-Analytics", "true");
346
}
347
},
348
onResponse({ response, request }) {
349
// Only cache GET requests with 2xx status
350
if (options.method === "GET" && response.ok) {
351
cacheResponse(String(request), response._data);
352
}
353
}
354
});
355
```