Client Middleware

The middleware in Vaadin is a special TypeScript async callback function that is executed by the frontend client during calls to 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.

When to Use a Middleware?

The middleware could be used for processing 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

The Middleware Structure

Here is an example logging middleware with the structure explained.

import { Middleware, MiddlewareContext, MiddlewareNext } from '@vaadin/flow-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 interfaces

The Vaadin middleware does not 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 a Middleware With a Client

To use a middleware, when the Vaadin TypeScript client is instantiated, include your middleware in the middlewares array option:

import { ConnectClient } from '@vaadin/flow-frontend';
import { MyLogMiddleware } from './my-log-middleware';

const client = new ConectClient({
  endpoint: '/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 related with that, it is better to avoid the modification of middlewares, or only modify the middlewares before the first call.

Changing the Request From a Middleware

To make a low-level modification on the request in a middleware, replace the context.request with a new Fetch API Request instance:

import { Middleware, MiddlewareContext, MiddlewareNext } from '@vaadin/flow-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 From a Middleware

A middleware can also replace the response by returning a custom Response instance:

import { Middleware, MiddlewareContext, MiddlewareNext } from '@vaadin/flow-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);
}