Skip to main content

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.

/auth-middleware.ts
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:

/api.ts
import { Api } from "api-def";

const API = new Api({
name: "My Backend",
baseUrl: "http://localhost:5000/v1",
middleware: [AuthMiddleware()],
});

export default API;

Events

NameUsage
beforeSendRuns before validation and the backend request.
beforeRequestRuns after request mutation and validation, immediately before the backend request.
successRuns once when the request succeeds.
attemptErrorRuns for each failed backend attempt and may respond or request a retry.
errorRuns once when the overall request is about to reject, including middleware and validation failures.
finallyRuns 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.