Middleware
Intercept requests and responses with the middleware pipeline
Overview
Middleware lets you hook into every API call made through a Whisper client. Each middleware can inspect or modify the request before it is sent and the response after it returns. Use middleware for logging, metrics, or request/response transformation.
Creating Middleware
A middleware is an object with optional onRequest and onResponse hooks:
import type { Middleware } from '@wardbox/whisper/core';
const logger: Middleware = {
name: 'logger',
onRequest(context) {
console.log(`-> ${context.method} ${context.url}`);
return context;
},
onResponse(response, context) {
console.log(`<- ${response.status} ${context.url}`);
return response;
},
};Both hooks can be synchronous or async. Return the (possibly modified) context or response.
Registering Middleware
Pass an array of middleware to createClient. They execute in registration order:
import { createClient } from '@wardbox/whisper/core';
const client = createClient({
apiKey: 'RGAPI-your-key-here',
middleware: [logger, metrics, customRetry],
});Execution Order
Middleware follows an onion model:
onRequesthooks run forward -- first registered middleware runs firstonResponsehooks run reverse -- first registered middleware runs last
Request flow: logger.onRequest -> metrics.onRequest -> [API call]
Response flow: metrics.onResponse -> logger.onResponseThis means the outermost middleware (first in the array) wraps the entire pipeline. A timing middleware registered first would measure the total time including all inner middleware.
Use Cases
Request Timing
const timer: Middleware = {
name: 'timer',
onRequest(context) {
(context as any)._startTime = Date.now();
return context;
},
onResponse(response, context) {
const duration = Date.now() - (context as any)._startTime;
console.log(`${context.methodId} took ${duration}ms`);
return response;
},
};Custom Headers
const customHeaders: Middleware = {
name: 'custom-headers',
onRequest(context) {
return {
...context,
headers: {
...context.headers,
'X-Custom-Source': 'my-app',
},
};
},
};Error Logging
const errorLogger: Middleware = {
name: 'error-logger',
onResponse(response, context) {
if (response.status >= 400) {
console.error(
`API error: ${response.status} on ${context.method} ${context.url}`,
);
}
return response;
},
};Request Context
The RequestContext object passed to onRequest contains:
| Property | Type | Description |
|---|---|---|
url | string | Full request URL |
method | string | HTTP method (GET, POST, etc.) |
headers | Record<string, string> | Request headers |
body | string | undefined | Request body (POST/PUT) |
route | PlatformRoute | RegionalRoute | Routing value for this request |
methodId | string | API method identifier (e.g., 'summoner-v4.getByPuuid') |
Response Object
The ApiResponse object passed to onResponse contains:
| Property | Type | Description |
|---|---|---|
data | T | Parsed response body |
status | number | HTTP status code |
headers | Record<string, string> | Response headers |
Pipeline Position
Middleware runs after the cache check and around the rate limiter and HTTP request:
Cache hit? -> return cached
Cache miss -> onRequest -> rate limit -> fetch -> onResponse -> cache storeCached responses skip middleware entirely for performance. If you need middleware to run on every call regardless of cache, disable caching for those endpoints with cacheTtl: { '/path': 0 }.