Quarkus and Web UI Development

In this blog post we will take advantage of the respective development modes of both Quarkus and Angular CLI and see how we can develop a zero turnaround web application backed by a RESTful API on Quarkus. While I am using Angular, the concepts are the same for other modern web application frameworks.

The source code for this blog can be found at https://github.com/kabir/blog-quarkus-ui-development. It contains a README which explains in detail how the application was set up and how we implemented each step, addressing problems we uncovered along our way.

You need to have Node, Yarn and Angular CLI installed on your system, and familiarity with Angular and Quarkus is assumed.

Clone the project, and go into its folder. First run:

$mvn -Dui.deps compile

This downloads all the dependencies needed to build our web application. Then run:

$mvn package -Dui.dev -Dui.proxy quarkus:dev

The -Dui system property enables a Maven profile to build the web application. The output directory for the web application webapp/dist/webapp gets bundled into the Quarkus application. We will talk about -Dui.proxy later on.

Go to http://localhost:8080 in your browser and take a look at our sample application. It contains a few different pages. Go to the one called Rest which makes a REST call. We can change the return value of the SampleServlet.hello() method. If you refresh the page you will see the changes made reflected automatically!

With what we have so far, if we were to make changes to the user interface in our web application, we would need to stop Quarkus and run the above command to build the web application again and restart quarkus. This is not what we want, so we take advantage of Angular’s proxy. React and Vue have something similar.

In another terminal window go into the webapp/ folder of the cloned project, and run:

$yarn proxy

This will start the Angular development server in proxy mode on port 4200. Go to http://localhost:4200 and you will see the same application as you saw earlier on port 8080, but served by the Angular development server while accessing the REST endpoints from the running Quarkus application. If you make any changes to any of the Angular components set up in app.component.ts you will see your changes reflected.

This is great!!! We can now be super-productive and make changes in both our front-end and our back-end, and see the changes immediately without needing to rebuild, repackage and restart our application!

Adjustments Summary

The example project has already been set up with the needed adjustments to work properly both as a bundled application and in development mode. These are the main tweaks:

  1. Handle the Angular routes in the UI rather than on the back-end. This is important for the bundled version (running on port 8080).

  2. Enable the Angular proxy to allow for the UI to call REST endpoints served by Quarkus.

    1. Signal to the back-end that the front-end is running in a proxy, i.e. not served by us. This is important if the back-end needs to redirect to a page in the front-end.

We will focus on the changes needed to make this work well, and not go into the details of how the application is set up.

Handle Angular Routes

Add a filter, AngularRouteFilter. The essence of its doFilter() method is:

chain.doFilter(request, response);

if (response.getStatus() == 404) {
    String path = request.getRequestURI().substring(
            request.getContextPath().length()).replaceAll("[/]+$", "");
    if (!FILE_NAME_PATTERN.matcher(path).matches()) {
        // We could not find the resource, i.e. it is not anything known to the server (i.e. it is not a REST
        // endpoint or a servlet), and does not look like a file so try handling it in the front-end routes.
        request.getRequestDispatcher("/").forward(request, response);

This filter is only needed when running the application bundled in Quarkus (port 8080). It is not needed when connecting to the application running in Angular (port 4200). It’s purpose is to allow us to bookmark pages in the application. All the server knows about are things like:

  • the REST endpoints

  • deployed servlets

  • the resources in the META-INF/resources/ folder of the application

The META-INF/resources/ in our case contains the application’s index.html and the transpiled resources.

Without this filter, if you go to one of the Angular routes directly (e.g if you try to refresh when on http://localhost:8080/rest which is the Rest page we saw earlier) Quarkus will think it is a file, a REST endpoint or a servlet. Since Quarkus has no knowledge of anything called /rest you end up with a 404 (Not Found) status. The filter checks the status code of the request after calling chain.doFilter(). If the status is 404 and does not seem to be a file, we forward this request to /, which in turn serves up the index.html file. By doing a forward the path and parameters of the request are preserved. Angular then figures out that /rest is one of its known routes and displays the appropriate page of the application. Once the web application is loaded in the browser, Angular takes over and handles all the internal links to other routes in the web application (as an example, if you are on http://localhost:8080 and click on the link taking you to http://localhost:8080/other there is no round-trip to the server).

There are other ways you can handle this too, e.g. by checking the path against a set of hard-coded known paths that are to be handled by the back-end, but for my purposes the above has worked very well. The key is to invoke:

request.getRequestDispatcher("/").forward(request, response);

if it is something that should be handled by Angular.

Set up the Angular proxy

The proxy is configured in proxy.conf.json. All REST calls to anything under /api will be passed to the back-end running on port 8080. To run the Angular development server with this configuration, we have added a proxy configuration to the scripts section of package.json.

In our case, we have a servlet which needs to redirect back to the front-end (something I found I needed to implement OAuth in my main project). That has a check for the -Dui.proxy system property we saw earlier when handling the /callback path in SampleServlet. If this property is set, we prepend https://localhost:4200 (the address of the Angular proxy) to the redirect URL if we find the proxy is running on port 4200.

Finally, DefaultComponent in app.component.ts has a direct link to our servlet running in the back-end. It has a check to see if the web application is running in the proxy (i.e. its port is 4200), and if this is the case it adjusts the url from /servlet/make-external-call to point to the Quarkus back-end running on port 8080.

Last words

We have seen how with a minimum amount of configuration we can have both the back-end and front-end of our application running in Quarkus’s and Angular’s respective development modes. During development this removes the need to rebuild the application when we change something. You can just code away, and see the changes when you refresh your browser. This is done by starting Quarkus with:

$mvn package -Dui.dev -Dui.proxy quarkus:dev

and starting the Angular application (from the webapp/ folder) with

$yarn proxy

Packaging for development

If you want to do a rebuild now and again to package the application and run it all in Quarkus run

$mvn package -Dui.dev quarkus:dev

By not passing in -Dui.proxy we disable the checks for whether the front-end runs in a proxy. -Dui.dev builds the web application so it is part of the Quarkus application.

Packaging for production

To package the application for production, use

$mvn package -Dui

-Dui builds the web application just like -Dui.dev, but it does more optimisations for production. Those optimisations take some time.

Packaging for cloud native

Finally to build a native image, make sure you have set up GraalVM as outlined here. Then build the application to generate the native executable.

$mvn package -Dui -Pnative

Running this we see the super-fast startup time Quarkus gives us in native mode:

2019-06-06 10:57:02,254 INFO  [io.quarkus] (main) Quarkus 0.15.0 started in 0.005s. Listening on: http://[::]:8080
2019-06-06 10:57:02,464 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb]