A logo showing the text blog.marcnuri.com
Español
Home»Java»React + Quarkus integration using Maven

Recent Posts

  • Fabric8 Kubernetes Client 7.2 is now available!
  • Connecting to an MCP Server from JavaScript using AI SDK
  • Connecting to an MCP Server from JavaScript using LangChain.js
  • The Future of Developer Tools: Adapting to Machine-Based Developers
  • Connecting to a Model Context Protocol (MCP) Server from Java using LangChain4j

Categories

  • Artificial Intelligence
  • Front-end
  • Go
  • Industry and business
  • Java
  • JavaScript
  • Legacy
  • Operations
  • Personal
  • Pet projects
  • Tools

Archives

  • May 2025
  • April 2025
  • March 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • August 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • July 2022
  • June 2022
  • May 2022
  • March 2022
  • February 2022
  • January 2022
  • December 2021
  • November 2021
  • October 2021
  • September 2021
  • August 2021
  • July 2021
  • January 2021
  • December 2020
  • November 2020
  • October 2020
  • September 2020
  • August 2020
  • July 2020
  • June 2020
  • May 2020
  • February 2020
  • January 2020
  • December 2019
  • October 2019
  • September 2019
  • July 2019
  • March 2019
  • November 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • December 2017
  • July 2017
  • January 2017
  • December 2015
  • November 2015
  • December 2014
  • March 2014
  • February 2011
  • November 2008
  • June 2008
  • May 2008
  • April 2008
  • January 2008
  • November 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • April 2007
  • March 2007

React + Quarkus integration using Maven

2020-09-21 in Java tagged Maven / Quarkus / React / YAKC by Marc Nuri | Last updated: 2021-05-23

Introduction

In this post I will show you how to serve a ReactJS (or any other JavaScript SPA) front-end application using a Java Quarkus Application as a back-end and static page server. The post also includes instructions on how to configure Maven to perform the front-end build tasks along with your application packaging.

This article is very similar to my 2017 Angular + Spring Boot integration using Gradle blog post, with some added features to enable React’s router to work.

Requirements

The technology stack required for this example is:

  • Node.js
  • React
  • Quarkus
  • Maven

The main goal is to achieve an npm install + build that integrates with the Maven resources plugin and deploys the distribution files to a directory where Quarkus can serve them. Quarkus will then act both as the back-end service and the gateway API to serve the front-end too.

You can find the code for these configurations in my YAKC – Kubernetes Dashboard quickstart application for YAKC (Yet Another Kubernetes Client).

Back-end (Quarkus)

In this new Microservice world, it makes sense that the front-end and back-end(s) are different services that are usually served and orchestrated by a gateway API. It’s hard to find those former web applications where front-end and back-end were part of the same service.

An image showing a microservice vs a monolith
An image showing a microservice vs a monolith

This distributed architecture makes sense for large enterprise projects, but sometimes we just want to prototype or quickly build a simple web application. That’s the time when you start missing those old monoliths and their simplicity that allowed you to quickly create and especially deploy your application.

The main goal is to achieve that Quarkus acts as a gateway API. So in addition to provide the REST API, it serves the front-end files too. This way we get to keep ReactJS as our main front-end technology, Quarkus as our back-end, and avoid all of the extra complexities needed for a 2 (or more) separate service deployment.

You may think that Quarkus already provides a way to serve static resources and that the solution is as simple as storing the front-end built files in this directory before building the back-end. However, this will not allow the React router to work and you wouldn’t be able to route /nested/front-end/routed/paths (Just try to refresh your browser in one of those nested paths).

GatewayResource class

1@Path("/")
2public class GatewayResource {
3
4  private static final String FALLBACK_RESOURCE = "/frontend/index.html";
5  private final ApiResource apiResource;
6
7  @Inject
8  public GatewayResource(ApiResource apiResource) {
9    this.apiResource = apiResource;
10  }
11
12  @Path("/api/v1")
13  public ApiResource getApiResource() {
14    return apiResource;
15  }
16
17  @GET
18  @Path("/")
19  public Response getFrontendRoot() throws IOException {
20    return getFrontendStaticFile("index.html");
21  }
22
23  @GET
24  @Path("/{fileName:.+}")
25  public Response getFrontendStaticFile(@PathParam("fileName") String fileName) throws IOException {
26    final InputStream requestedFileStream = GatewayResource.class.getResourceAsStream("/frontend/" + fileName);
27    final InputStream inputStream = requestedFileStream != null ?
28      requestedFileStream :
29      GatewayResource.class.getResourceAsStream(FALLBACK_RESOURCE);
30    final StreamingOutput streamingOutput = outputStream -> IOUtils.copy(inputStream, outputStream);
31    return Response
32      .ok(streamingOutput)
33      .cacheControl(CacheControl.valueOf("max-age=900"))
34      .type(URLConnection.guessContentTypeFromStream(inputStream))
35      .build();
36  }
37
38}

This class is the main entry point for any HTTP request that the application receives.

The first method, getApiResource delegates any request to /api/v1 to the ApiResource subresource instance that will take care of it.

The getFrontendRoot will take care of any request to the root path /by requesting the index.html file to the getFrontendStaticFile which I’ll describe next.

Finally the getFrontendStaticFile will take care of the rest of the requests. The method first tries to locate any file with the provided file name in the /frontend resource directory. This directory should be packaged during the application build phase and contains the ReactJS built application.

If no file is found with this name, it will serve the index.html file. This is the part that allows the React router to work. Any request not matched either by the API subresource or a front-end file will serve the React application which will then take care of performing a “front-end” routing.

Front-end (React)

Your complete front-end application may be located anywhere within your project, in my case I’ve located it under src/main/frontend. Besides noting the path location for your React application, you don’t really need any extra configuration for this procedure to work.

Maven build

For the final step to integrate the React front-end with the Quarkus back-end you need to tune the Maven build.

Resources

1<!-- ... -->
2  <build>
3    <resources>
4      <resource>
5        <directory>src/main/resources</directory>
6      </resource>
7      <resource>
8        <directory>src/main/frontend/build</directory>
9        <targetPath>frontend</targetPath>
10      </resource>
11    </resources>
12    <!-- ... -->
13  </build>
14  <!-- ... -->

In this first excerpt, I’ve added a new entry to the build resources configuration. Besides the standard src/main/resources, the directory where the React application is built src/main/frontend/build will also be assembled and copied to target/classes/frontend directory. If you recall, in the GatewayResource class, I was loading static resources from this directory.

Npm build + install

1<!-- ... -->
2  <profiles>
3    <profile>
4      <id>build-frontend</id>
5      <build>
6        <plugins>
7          <plugin>
8            <groupId>org.codehaus.mojo</groupId>
9            <artifactId>exec-maven-plugin</artifactId>
10            <version>${version.exec-maven-plugin}</version>
11            <executions>
12              <execution>
13                <id>npm-install</id>
14                <phase>generate-resources</phase>
15                <goals>
16                  <goal>exec</goal>
17                </goals>
18                <configuration>
19                  <workingDirectory>src/main/frontend</workingDirectory>
20                  <executable>npm</executable>
21                  <arguments>
22                    <argument>install</argument>
23                  </arguments>
24                </configuration>
25              </execution>
26              <execution>
27                <id>npm-build</id>
28                <phase>generate-resources</phase>
29                <goals>
30                  <goal>exec</goal>
31                </goals>
32                <configuration>
33                  <workingDirectory>src/main/frontend</workingDirectory>
34                  <executable>npm</executable>
35                  <arguments>
36                    <argument>run</argument>
37                    <argument>build</argument>
38                  </arguments>
39                </configuration>
40              </execution>
41            </executions>
42          </plugin>
43        </plugins>
44      </build>
45    </profile>
46    <!-- ... -->
47  </profiles>

The second excerpt is used to run npm install and npm run build from Maven. I added this as a separate profile because I usually perform these steps manually while in my local machine. I only invoke them in the CI, so my CI build step looks like mvn -Pbuild-frontend clean package. However, there’s no problem in adding these configurations directly to your pom.xml build section.

As you can see I configured 2 executions for the Exec Maven Plugin. The first one will invoke npm install using the front-end home directory (src/main/frontend) as its working directory. The second execution has a similar configuration but runs npm run build instead.

Execution

Once your application is packaged, you can run it either by running the generated jar (java -jar ./target...) file or by running mvn quarkus:dev.

A recording of how front-end routing works with Quarkus
A recording of how front-end routing works with Quarkus

Development mode

If you want to enjoy and benefit from both Quarkus and React hot/live reloading you will need some extra configuration.

In this case, we will run the front-end using the standard npm start and pointing our browser to http://localhost:3000. And we’ll run our back-end using mvn quarkus:dev in http://localhost:8080.

Back-end

Since the front-end is served from a different URL, we need to enable CORS in Quarkus.

We can do this by modifying our application.properties to include:

1quarkus.http.cors=true
2quarkus.http.cors.origins=http://localhost:3000
3quarkus.http.cors.headers=accept, origin, authorization, content-type, x-requested-with
4quarkus.http.cors.methods=GET,POST,DELETE,OPTIONS

These properties enable CORS to allow requests from the front-end dev origin. You can modify the entries to make it more or less restrictive depending on your needs.

Front-end

For the front-end part, we need to tune our React application to hit different back-end endpoints depending on the environment we’re running it on.

In my case, since I’m using create-react-app, I added two environment property files:

.env for production:

1REACT_APP_API_URL=/api/v1

.env.development for dev:

1REACT_APP_API_URL=http://localhost:8080/api/v1

Once these two files are ready, create-react-app takes care of the appropriate replacements in your application depending on your target environment. In order to use those values, you’ll need to reference the URL using the process.env.REACT_APP_API_URL variable:

1const requestDelete = async pod => {
2  await fetch(
3    `${process.env.REACT_APP_API_URL}/pods/${metadata.selectors.namespace(pod)}/${metadata.selectors.name(pod)}`,
4      {method: 'DELETE'}
5        );
6      }

Conclusion

In this post, I showed you how to serve both a JavaScript front-end and a Java back-end application using Quarkus. Quarkus acts as an API gateway and either serves the front-end static files or processes the back-end request using resources and services. I’ve also shown you how to combine and use hot reload in React and Quarkus by tuning the settings for each application.

I’ve extracted the code for this post from my YAKC – Kubernetes Dashboard quickstart. You can learn more by visiting YAKC’s GitHub project site. You can also see the related code here.

Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Comments in "React + Quarkus integration using Maven"

  • Avatar for Jeff Rogers
    Jeff Rogers
    2020-12-18 14:12
    This is very interesting, especially the gateway api. I'm new to microservices, but my understanding of them is that they are able to be deployed individually. It looks like the different resources in this example would be deployed in a single Quarkus instance.

    Is this still considered microservices?

    Could you expand on this topic a bit more on how the different services would be deployed individually using the quarkus rest client?

    Thank you for sharing this article.
    • Avatar for Marc Nuri
      Marc Nuri
      2020-12-20 08:32
      Is this still considered microservices?

      I wouldn't consider what I depict in this post a microservice on its own. However, if this monolithic application (frontend+backend) was to be deployed as part of a distributed system and the architecture was designed according to a microservice philosophy (do one thing and do it well), I guess you could consider that as a microservice (micromonolith of sorts ;p).

      From my experience, front-ends are usually deployed as separate microservices. An API gateway (such as Zuul, NGINX, Traefik) is then used to redirect traffic to one service or another depending on certain parameters (e.g. HTTP path). Any of the resources served by the ApiResource class could be "easily" deployed as separate services (the left part of the diagram). You could then tune the gateway to serve traffic to those services by using a pattern matcher on the HTTP path.

      In this example the GatewayResource class represents this otherwise external API gateway. So another thing you could do is use this service as the front-end + API gateway server and use the Quarkus REST client to redirect traffic to other microservices (I've also seen this approach in some projects). The advantage of this is that you could use programmatic rules to compute where traffic should get redirected. However, this doesn't follow the microservice philosophy since any change to the front-end would require a redeploy of the gateway (not doing a single thing and doing it well). You'd also lose the option (or at least make it much harder) to do front-end canary deployments, etc.

      I'm not sure if this clarified your doubts or introduced new ones. Anyway, thanks for reading ;)

  • Avatar for Jeff Rogers
    Jeff Rogers
    2021-06-01 04:43
    I'm not a react developer, but I do use quarkus. Your GET method for index.html when a page is refreshed worked nice for my quarks/angular application.

    Thanks for sharing
  • Avatar for Guillaume Fortin
    Guillaume Fortin
    2021-06-03 20:35
    Hi, I have an issue with the code. When I call the api/v1, the app reacts the way it should however when I call the root it looks like the React App doesn't trigger and I'm staying with a blank page.
    If I start the React app independently it's working fine.

    The root path of my quarkus app is /klon/. it's not the default /

    What could be the issue?
    thanks
    • Avatar for Marc Nuri
      Marc Nuri
      2021-06-05 03:47
      I'd guess that your front-end app hasn't been built or deployed.

      In this example there is a build-frontend Maven profile that runs npm install and npm run build for you. Are you sure you're invoking the maven command correctly (mvn -Pbuild-frontend clean package)?

Post navigation
Rollout Restart Kubernetes Deployment from Java using YAKCKubernetes 1.19 Ingress API from Java using YAKC
© 2007 - 2025 Marc Nuri