Overview
The Hooks interface provides lifecycle hooks that allow you to intercept and customize various stages of the HTTP request/response cycle. Hooks are useful for logging, authentication, error handling, progress tracking, and other cross-cutting concerns.
Type Definition
interface Hooks<TCallApiContext extends CallApiContext = DefaultCallApiContext> {
onError?: (context: ErrorContext) => Awaitable<unknown>;
onRequest?: (context: RequestContext) => Awaitable<unknown>;
onRequestError?: (context: RequestErrorContext) => Awaitable<unknown>;
onRequestReady?: (context: RequestContext) => Awaitable<unknown>;
onRequestStream?: (context: RequestStreamContext) => Awaitable<unknown>;
onResponse?: (context: ResponseContext) => Awaitable<unknown>;
onResponseError?: (context: ResponseErrorContext) => Awaitable<unknown>;
onResponseStream?: (context: ResponseStreamContext) => Awaitable<unknown>;
onRetry?: (context: RetryContext) => Awaitable<unknown>;
onSuccess?: (context: SuccessContext) => Awaitable<unknown>;
onValidationError?: (context: ValidationErrorContext) => Awaitable<unknown>;
}
Hook Lifecycle
Hooks are called in this order during a request:
- onRequest - Before the request is sent
- onRequestReady - Just before fetch is called
- onRequestStream - During upload (if streaming)
- Fetch happens
- onResponseStream - During download (if streaming)
- onResponse - When response is received
- onSuccess OR onResponseError - Based on HTTP status
- onValidationError - If validation fails
- onRequestError - If network error occurs
- onRetry - If request is retried
- onError - For any error
Available Hooks
onRequest
onRequest
(context: RequestContext) => Awaitable<unknown>
Called before the HTTP request is sent and before any internal processing begins.Use Cases:
- Add authentication headers
- Modify request headers or body
- Log outgoing requests
- Add request timestamps
const api = createFetchClient({
baseURL: 'https://api.example.com',
onRequest: ({ request, options }) => {
console.log('Sending request:', request.method, request.url);
// Add auth header
request.headers['Authorization'] = `Bearer ${getToken()}`;
// Add timestamp
options.meta = {
...options.meta,
requestStartTime: Date.now()
};
}
});
onRequestReady
onRequestReady
(context: RequestContext) => Awaitable<unknown>
Called just before the HTTP request is sent, after the request has been processed.onRequestReady: ({ request }) => {
console.log('Final request headers:', request.headers);
}
onResponse
onResponse
(context: ResponseContext) => Awaitable<unknown>
Called when any HTTP response is received, regardless of status code.Use Cases:
- Log all responses
- Track response metrics
- Handle rate limiting headers
onResponse: ({ response, data, error, options }) => {
const duration = Date.now() - options.meta?.requestStartTime;
console.log('Response received:', {
status: response.status,
duration,
success: error === null
});
// Check rate limit headers
const remaining = response.headers.get('X-RateLimit-Remaining');
if (remaining && parseInt(remaining) < 10) {
console.warn('Approaching rate limit');
}
}
onSuccess
onSuccess
(context: SuccessContext) => Awaitable<unknown>
Called when a successful response (2xx status) is received.Use Cases:
- Cache successful responses
- Track successful operations
- Post-process response data
const cache = new Map();
onSuccess: ({ data, response, request }) => {
console.log('Request succeeded:', data);
// Cache GET requests
if (request.method === 'GET') {
cache.set(response.url, data);
}
// Track analytics
analytics.track('api_success', {
endpoint: response.url,
status: response.status
});
}
onError
onError
(context: ErrorContext) => Awaitable<unknown>
Called when any error occurs (HTTP errors, network errors, or validation errors).Use Cases:
- Centralized error logging
- Error reporting to monitoring service
- Display user notifications
onError: ({ error, response, options }) => {
console.error('Request failed:', error.message);
// Log to monitoring service
errorLogger.log({
type: error.name,
message: error.message,
status: response?.status,
url: options.baseURL
});
// Show user notification
if (error.name === 'HTTPError') {
toast.error(`Request failed: ${error.message}`);
}
}
onResponseError
onResponseError
(context: ResponseErrorContext) => Awaitable<unknown>
Called when an HTTP error response (4xx, 5xx) is received.Use Cases:
- Handle authentication errors
- Parse and log error responses
- Trigger error recovery
onResponseError: async ({ error, response, request }) => {
console.error('HTTP error:', response.status, error.errorData);
// Handle 401 Unauthorized
if (response.status === 401) {
await refreshAuthToken();
// Optionally retry the request
}
// Handle 429 Rate Limit
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
console.warn(`Rate limited. Retry after ${retryAfter}s`);
}
}
onRequestError
onRequestError
(context: RequestErrorContext) => Awaitable<unknown>
Called when a network-level error occurs (connection failure, timeout, etc.).Use Cases:
- Handle network failures
- Implement offline detection
- Log connection issues
onRequestError: ({ error }) => {
console.error('Network error:', error.message);
// Detect specific error types
if (error.name === 'TimeoutError') {
toast.error('Request timed out. Please try again.');
} else if (error.name === 'TypeError') {
toast.error('Network connection failed.');
// Check online status
if (!navigator.onLine) {
console.log('User is offline');
}
}
}
onValidationError
onValidationError
(context: ValidationErrorContext) => Awaitable<unknown>
Called when request or response data fails schema validation.Use Cases:
- Log validation failures
- Send validation errors to monitoring
- Handle schema mismatches
onValidationError: ({ error }) => {
console.error('Validation error:', {
cause: error.issueCause, // 'request' or 'response'
message: error.message,
errors: error.errorData
});
// Log to error tracking
if (error.issueCause === 'response') {
errorTracker.log('schema_mismatch', {
endpoint: response?.url,
errors: error.errorData
});
}
}
onRetry
onRetry
(context: RetryContext) => Awaitable<unknown>
Called before each retry attempt.Use Cases:
- Log retry attempts
- Implement custom backoff logic
- Track retry metrics
onRetry: ({ error, retryAttemptCount, options }) => {
console.log(`Retrying request (attempt ${retryAttemptCount})`, {
error: error.message,
maxRetries: options.retry
});
// Track retry metrics
analytics.track('api_retry', {
attempt: retryAttemptCount,
errorType: error.name
});
}
onRequestStream
onRequestStream
(context: RequestStreamContext) => Awaitable<unknown>
Called during upload stream progress tracking.Use Cases:
- Display upload progress bars
- Track upload metrics
- Implement upload cancellation
onRequestStream: ({ event, requestInstance }) => {
const progress = (event.loaded / event.total) * 100;
console.log(`Upload progress: ${progress.toFixed(2)}%`);
// Update progress bar
progressBar.style.width = `${progress}%`;
// Track upload speed
const speed = event.loaded / (Date.now() - event.startTime);
console.log(`Upload speed: ${formatBytes(speed)}/s`);
}
onResponseStream
onResponseStream
(context: ResponseStreamContext) => Awaitable<unknown>
Called during download stream progress tracking.Use Cases:
- Display download progress bars
- Track download metrics
- Implement download cancellation
onResponseStream: ({ event, response }) => {
const progress = (event.loaded / event.total) * 100;
console.log(`Download progress: ${progress.toFixed(2)}%`);
// Update UI
downloadProgress.textContent = `${progress.toFixed(0)}%`;
downloadProgress.setAttribute('value', progress);
}
Hook Contexts
All hooks receive a context object with relevant information. Here are the common context properties:
RequestContext
baseConfig
BaseCallApiConfig
required
Base configuration object passed to createFetchClient.
Instance-specific configuration object.
options
CallApiExtraOptions
required
Merged options combining base config, instance config, and defaults.
request
CallApiRequestOptions
required
Merged request object ready to be sent. Can be modified in onRequest hooks.
SuccessContext
Extends RequestContext with:
The parsed response data.
ErrorContext
Extends RequestContext with:
Error information object.
The Response object if available, or null for network errors.
RetryContext
Extends ErrorContext with:
Current retry attempt number (1-indexed).
Stream Contexts
event
StreamProgressEvent
required
Progress event with loaded, total, and timing information.
Hook Arrays
You can provide multiple hooks as an array:
const api = createFetchClient({
baseURL: 'https://api.example.com',
onRequest: [
({ request }) => {
console.log('Hook 1: Adding auth');
request.headers['Authorization'] = `Bearer ${token}`;
},
({ request }) => {
console.log('Hook 2: Adding timestamp');
request.headers['X-Timestamp'] = Date.now().toString();
},
async ({ request }) => {
console.log('Hook 3: Async operation');
await logRequest(request);
}
]
});
Hook Execution Mode
Control how multiple hooks execute:
hooksExecutionMode
'parallel' | 'sequential'
default:"parallel"
- parallel: All hooks execute simultaneously via Promise.all()
- sequential: Hooks execute one by one in registration order
const api = createFetchClient({
baseURL: 'https://api.example.com',
hooksExecutionMode: 'sequential',
onRequest: [
async ({ request }) => {
// This completes before hook 2 starts
await operation1();
},
async ({ request }) => {
// This starts after hook 1 completes
await operation2();
}
]
});
Examples
Complete Logging Setup
const api = createFetchClient({
baseURL: 'https://api.example.com',
onRequest: ({ request, options }) => {
console.log('→ Request:', request.method, request.url);
options.meta = { startTime: Date.now() };
},
onResponse: ({ response, options }) => {
const duration = Date.now() - options.meta.startTime;
console.log('← Response:', response.status, `${duration}ms`);
},
onSuccess: ({ data }) => {
console.log('✓ Success:', data);
},
onError: ({ error }) => {
console.error('✗ Error:', error.message);
}
});
Authentication with Token Refresh
let authToken = 'initial-token';
const api = createFetchClient({
baseURL: 'https://api.example.com',
onRequest: ({ request }) => {
request.headers['Authorization'] = `Bearer ${authToken}`;
},
onResponseError: async ({ response, error }) => {
if (response.status === 401) {
console.log('Token expired, refreshing...');
authToken = await refreshAuthToken();
}
}
});
Progress Tracking
const api = createFetchClient({
baseURL: 'https://api.example.com',
onRequestStream: ({ event }) => {
const percent = (event.loaded / event.total) * 100;
updateUploadProgress(percent);
},
onResponseStream: ({ event }) => {
const percent = (event.loaded / event.total) * 100;
updateDownloadProgress(percent);
}
});
See Also