Docs

Reactive Endpoints

Reactive endpoints allow you to stream data to the client without the traditional HTTP request-response pattern.

Although traditional server calls work fine in most cases, sometimes you need different tools. Reactor is one of these, and can help you stream data to clients — and it fits well into a non-blocking application. Whenever the simple request-response pattern doesn’t suit your needs, you might consider Reactor. Multi-user applications, infinite data streaming, and retries are all good examples of what you can do with it.

If you want to know more about Reactor, see their curated learning page.

Although getting comfortable with creating streaming, non-blocking applications requires some effort, Hilla allows you to take advantage of Reactor in minutes. To learn the basics of reactive endpoints, read the Hilla blog article on the subject.

Familiarizing

Before looking at the API details, you might familiarize yourself with the basics of reactive endpoints by writing a simple application that can stream the current time of the server to the client. The task of this example is to send the server time to all subscribed clients, with an interval of one second between each message.

Start by creating a Hilla application like so:

npx @hilla/cli init flux-clock

Server-Side Endpoints

Next you’ll need to create a server-side endpoint. To do this, open your application in your favorite IDE or editor.

Create a new endpoint in a package of your choice and name it ReactiveEndpoint. Add these two methods to it:

@AnonymousAllowed
public Flux<@Nonnull String> getClock() {
    return Flux.interval(Duration.ofSeconds(1))
            .onBackpressureDrop()
            .map(_interval -> new Date().toString());
}

@AnonymousAllowed
public EndpointSubscription<@Nonnull String> getClockCancellable() {
    return EndpointSubscription.of(getClock(), () -> {
        LOGGER.info("Subscription has been cancelled");
    });
}
Note
You can click on the expand code icon at the top-right of the code snippet to get the whole file.

The first method, getClock(), creates a Flux that streams the server time each second. It uses onBackpressureDrop() to avoid saturation in case some messages can’t be sent to the client in real time. In this use case, it’s acceptable to lose some messages.

The second method, getClockCancellable(), returns the same Flux, but wrapped in an EndpointSubscription. This is a Hilla-specific Flux wrapper which allows you to execute something on the server side if the subscription is cancelled by the client. In this example, a log message is produced.

Showing Messages

Next, you’ll need to create a view to show the messages. You’ll use these methods in your client views.

Create the frontend/views/reactive/ReactiveView.tsx file to add a new view. This looks like the default "Hello world" example, customized with the code that follows.

import { Subscription } from "@hilla/frontend";
import { Button } from "@hilla/react-components/Button.js";
import { TextField } from "@hilla/react-components/TextField.js";
import { getClockCancellable } from "Frontend/generated/ReactiveEndpoint";
import { useState } from "react";

export default function ReactiveView() {
  const [serverTime, setServerTime] = useState("");
  const [subscription, setSubscription] = useState<Subscription<string> | undefined>();

  const toggleServerClock = async () => {
    if (subscription) {
      subscription.cancel();
      setSubscription(undefined);
    } else {
      setSubscription(getClockCancellable().onNext((time) => {
        setServerTime(time);
      }));
    }
  }

  return (
      <section className="flex p-m gap-m items-end">
        <TextField label="Server time" value={serverTime} readonly />
        <Button onClick={toggleServerClock}>Toggle server clock</Button>
      </section>
  );
}

Then, add the new view to the router by importing it in frontend/routes.tsx with this:

import ReactiveView from './views/reactive/ReactiveView';

Declare it with:

{ path: '/reactive', element: <ReactiveView />, handle: { icon: 'la la-file', title: 'Reactive' } },

right after the /about path.

Run the Application

Now, start your new application as usual:

mvnw

Your new view should now be available in the application. When the Toggle server clock button is clicked, the time appears in the text field and updates each second. You can stop it by clicking again.

API Overview

The generated TypeScript endpoint methods return a Subscription<T> object, which offers these methods:

cancel()

Cancel the subscription.

onNext()

Get the next message in the Flux.

onError()

Get errors that can happen in the Flux.

onComplete()

Be informed when the Flux has no more messages to send.

context()

Bind to the context, which allows the application automatically to close it when the view is detached.