Middleware
When we make requests a lot of the time there is some consistent logic between all of our endpoints such as authentication or error handling. This is where middleware come in!
Defining & Adding Middleware
Middleware are objects whose keys are event names and values are listeners. Listeners may be synchronous or asynchronous.
import { RequestMiddleware } from "api-def";
const getAuthToken = (): Promise<string> => {
return /*... */;
};
/*
Add auth header to requests
*/
const AuthMiddleware = (): RequestMiddleware => {
return {
// called before a send occurs
beforeSend: async (context) => {
const authToken = await getAuthToken();
if (authToken) {
context.updateHeaders({
Authorization: `Bearer ${authToken}`,
});
}
},
};
};
export default AuthMiddleware;
Now that we have our fancy new middleware we can add it to our api:
import { Api } from "api-def";
const API = new Api({
name: "My Backend",
baseUrl: "http://localhost:5000/v1",
middleware: [AuthMiddleware()],
});
export default API;
Events
| Name | Usage |
|---|---|
beforeSend | Runs before validation and the backend request. |
beforeRequest | Runs after request mutation and validation, immediately before the backend request. |
success | Runs once when the request succeeds. |
attemptError | Runs for each failed backend attempt and may respond or request a retry. |
error | Runs once when the overall request is about to reject, including middleware and validation failures. |
finally | Runs once after every successful or failed request. |
error and finally are observers; their return values are ignored. Use attemptError when middleware needs to recover from a failed attempt or trigger a retry.
Event Results
Your event listeners can also return special objects that will trigger certain things
Respond
Applicable in the beforeSend, beforeRequest, and attemptError events.
Your middleware can respond instead of going to the network, as used by the built-in client cache middleware.
return {
type: "respond",
response: {
status: 200,
data: /* ... */,
headers: {}
}
};
Retry
Applicable in the attemptError event.
Trigger a retry despite the retry option. Be careful of causing infinite loops with this!
return {
type: "retry"
};
Cleanup
Use finally to release resources registered for a request. Inspect context.response and context.error to determine the outcome:
const cleanupMiddleware: RequestMiddleware = {
finally: (context) => {
pendingRequests.delete(context.id);
},
};
Endpoint middleware
Pass middleware to an individual endpoint when it should run after API-level middleware:
const endpoint = API.endpoint().build({
id: "fetch_data",
method: "get",
path: "/data",
middleware: [AuthMiddleware()],
});
Request mutation
Middleware can update the request before the backend runs. These changes affect only the current request:
const rewriteRequest: RequestMiddleware = {
beforeSend: (context) => {
context.updateBaseUrl("https://backup.example.com");
context.updatePath("/health");
context.updateMethod("get");
context.updateBody({ source: "fallback" });
},
};
Use context.requestConfig to inspect the merged API, endpoint, and request configuration. context.state is not a public property; middleware-only state is available at context.requestConfig.state.