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: "https://api.example.com/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.