Docs

TypeScript Client

The ConnectClient that sends the calls of React views to browser-callable services, and the middleware that intercepts them.

The generated modules of browser-callable services send their calls through a ConnectClient from the @vaadin/hilla-frontend package. Vaadin generates a default client in src/main/frontend/generated/connect-client.default.ts, which all the generated modules share:

Source code
TypeScript
import { ConnectClient } from '@vaadin/hilla-frontend';

const client = new ConnectClient({ prefix: 'connect' });

export default client;

Don’t edit the generated file, because Vaadin overwrites it.

A Client of Your Own

To configure the client, create src/main/frontend/connect-client.ts, with the client as its default export. When this file exists, the generated modules use its client, and Vaadin doesn’t generate connect-client.default.ts:

Source code
src/main/frontend/connect-client.ts
import { ConnectClient } from '@vaadin/hilla-frontend';
import { logMiddleware } from './log-middleware';

const client = new ConnectClient({ prefix: 'connect', middlewares: [logMiddleware] });

export default client;

The constructor takes these options:

prefix

The path of the services, relative to the base URL of the application. It must match the server, as described in Service URLs. Defaults to connect.

middlewares

The middleware that intercepts the calls.

Calls Without a Generated Module

The generated functions call the call() method of the client, which a view can also call directly. It takes the service name, the method name, an object with the arguments by parameter name, and the optional request options. It returns a Promise of the return value. Import the client from Frontend/connect-client when the application has a client of its own:

Source code
TypeScript
import client from 'Frontend/generated/connect-client.default';

const result = await client.call('CounterService', 'addOne', { number: 1 });

The subscribe() method does the same for reactive services, and returns a Subscription.

Middleware

Middleware intercepts each call of the client. It can, for example, log or time the calls, add headers, or return a response without calling the server. Middleware is an async function that takes these arguments:

context

The call. Its endpoint, method, and params properties are the arguments of call(). Its request property is the Fetch API Request that the client sends.

next

A function that passes the context on to the next middleware, or sends the request, and returns the Response.

Middleware gets the response before the client checks it, so it sees responses with an error status as well. The client turns the response into a return value or an error after all the middleware has run. Middleware doesn’t intercept the values of reactive services.

The following middleware logs each call:

Source code
log-middleware.ts
import type { MiddlewareContext, MiddlewareNext } from '@vaadin/hilla-frontend';

export async function logMiddleware(context: MiddlewareContext, next: MiddlewareNext): Promise<Response> {
  const { endpoint, method, params } = context;
  console.log(`Calling ${endpoint}.${method} with ${JSON.stringify(params)}`);

  const response = await next(context);

  // Clone the response before reading its body
  const body = await response.clone().text();
  console.log(`Received ${response.status}: ${body}`);
  return response;
}

Give the middleware to the client of your own, as shown in A Client of Your Own. The middleware runs in the order of the array. A call uses the middleware that the client has when the call starts. A middleware can also be an object with an invoke(context, next) method.

To change the request, replace context.request before calling next. The following middleware sends the calls of one service to another server:

Source code
TypeScript
import type { MiddlewareContext, MiddlewareNext } from '@vaadin/hilla-frontend';

export async function dispatchMiddleware(context: MiddlewareContext, next: MiddlewareNext): Promise<Response> {
  if (context.endpoint === 'ExternalService') {
    const url = context.request.url.replace('https://my-app.example.com', 'https://external.example.com');
    context.request = new Request(url, context.request);
  }
  return next(context);
}

To answer a call without calling the server, return a Response instead of calling next:

Source code
TypeScript
import type { MiddlewareContext, MiddlewareNext } from '@vaadin/hilla-frontend';

export async function stubMiddleware(context: MiddlewareContext, next: MiddlewareNext): Promise<Response> {
  if (context.endpoint === 'StubService') {
    return new Response('{}');
  }
  return next(context);
}

The @vaadin/hilla-frontend package also has an InvalidSessionMiddleware, which handles calls that fail because the session of the user has expired. See Handling Session Expiration.

Updated –