Client Middleware
The middleware in Hilla is a special TypeScript async callback function that’s executed by the frontend during calls to the backend. It intercepts the request and the response for every call. The middleware has access to the call context information, including the endpoint and the method names, the supplied parameters, and other client call options.
Client middleware is an advanced topic and not recommended for most users.
When Useful
Middleware can be used to process requests and their responses. The typical use cases are:
-
performance measurement
-
logging the requests
-
retrying
-
batching
-
caching the response
-
custom request and response headers and body handling
Middleware Structure
Here is an example of logging middleware, with an explanation of the structure.
import { Middleware, MiddlewareContext, MiddlewareNext } from '@hilla/frontend';
// A middleware is an async function, that receives the `context` and `next`
export const MyLogMiddleware: Middleware = async function(
context: MiddlewareContext,
next: MiddlewareNext
) {
// The context object contains the call arguments. See the `call` method
// of the `ConnectClient` class for their descriptions.
const {endpoint, method, params} = context;
console.log(
`Sending request to endpoint: ${endpoint} ` +
`method: ${method} ` +
`parameters: ${JSON.stringify(params)} `
);
// Also, the context contains the `request`, which is a Fetch API `Request`
// instance to be sent over the network.
const request: Request = context.request;
console.log(`${request.method} ${request.url}`);
// Call the `next` async function to send the request and get the response.
const response: Response = await next(context);
// The response is a Fetch API `Response` object.
console.log(`Received response: ${response.status} ${response.statusText}`);
console.log(await response?.text());
// A middleware returns a response.
return response;
}
Note
|
Request and Response are Fetch API interfacesThe Hilla middleware doesn’t invent a new data structure to represent the network request and response, but uses the interfaces declared by the Fetch API specification instead. See the MDN web docs to learn more about the Request API and Response API. |
Using Middleware with a Client
To use middleware, when the Hilla TypeScript client is instantiated, include your middleware in the middlewares
array option:
import { ConnectClient } from '@hilla/frontend';
import { MyLogMiddleware } from './my-log-middleware';
const client = new ConnectClient({
prefix: '/connect',
middlewares: [MyLogMiddleware]
});
export default client;
Alternatively, you can modify the middlewares
array property on the existing client, for example if you use a generated client:
import client from 'Frontend/generated/connect-client.default';
import { MyLogMiddleware } from './my-log-middleware';
client.middlewares = [MyLogMiddleware];
Caution
|
Modifying middleware at runtime
If you modify the middlewares array, only calls initiated after the modification use the new middlewares array.
To avoid issues connected with this, it’s better to avoid modifying middlewares , or to modify middlewares only before the first call.
|
Changing a Request Using Middleware
To make a low-level modification of the request in middleware, replace the context.request
with a new Fetch API Request
instance:
import { Middleware, MiddlewareContext, MiddlewareNext } from '@hilla/frontend';
// An example middleware that uses a different server for selected requests
export const MyApiDispatcherMiddleware: Middleware = async function(
context: MiddlewareContext,
next: MiddlewareNext
) {
if (context.endpoint === 'ExternalEndpoint') {
const url = context.request.url.replace(
'https//my-app.example.com',
'https://external-endpoint.example.com'
);
context.request = new Request(url, context.request);
}
return await next(context);
};
Custom Response using Middleware
Middleware can also replace the response by returning a custom Response
instance:
import { Middleware, MiddlewareContext, MiddlewareNext } from '@hilla/frontend';
// An example middleware that returns an empty response instead of calling the backend endpoint
export const MyStubMiddleware: Middleware = async function(
context: MiddlewareContext,
next: MiddlewareNext
) {
if (context.endpoint === 'StubEndpoint') {
//
return new Response('{}');
}
return await next(context);
}