Book Image

Full Stack Quarkus and React

By : Marc Nuri San Felix
Book Image

Full Stack Quarkus and React

By: Marc Nuri San Felix

Overview of this book

React has established itself as one of the most popular and widely adopted frameworks thanks to its simple yet scalable app development abilities. Quarkus comes across as a fantastic alternative for backend development by boosting developer productivity with features such as pre-built integrations, application services, and more that bring a new, revolutionary developer experience to Java. To make the best use of both, this hands-on guide will help you get started with Quarkus and React to create and deploy an end-to-end web application. This book is divided into three parts. In the first part, you’ll begin with an introduction to Quarkus and its features, learning how to bootstrap a Quarkus project from the ground up to create a tested and secure HTTP server for your backend. The second part focuses on the frontend, showing you how to create a React project from scratch to build the application’s user interface and integrate it with the Quarkus backend. The last part guides you through creating cluster configuration manifests and deploying them to Kubernetes as well as other alternatives, such as Fly.io. By the end of this full stack development book, you’ll be confident in your skills to combine the robustness of both frameworks to create and deploy standalone, fully functional web applications.
Table of Contents (21 chapters)
1
Part 1– Creating a Backend with Quarkus
8
Part 2– Creating a Frontend with React
14
Part 3– Deploying Your Application to the Cloud

Project structure and dependencies

To start exploring the project’s structure, let us go through the following steps:

  1. Extract the generated ZIP package from the preceding bootstrapping exercise to the project’s definitive location. I extracted the ZIP to my dedicated directory for projects – you can extract it wherever you prefer but make sure you remember this location since this is the project we will be working on throughout the book.
  2. Open the project in IntelliJ IDEA or the IDE of your choice.

IntelliJ should automatically detect your Maven project and load its dependencies. In case it doesn’t, you can perform this step manually by right-clicking the pom.xml file and clicking on the Add as Maven Project menu item:

Figure 1.3 – A screenshot of IntelliJ IDEA and the Add as Maven Project context menu

Figure 1.3 – A screenshot of IntelliJ IDEA and the Add as Maven Project context menu

Let’s now explore the content and structure of the project and the provided sample code.

Maven Wrapper

The project includes a Maven Wrapper setup. Maven Wrapper is a tool that allows project users to run a consistent version of Maven across different build environments. The tool also allows you to run Maven without the need to have a global Maven installation. The project includes the .mvn directory and the mvnw and mvnw.cmd executable files.

You should be able to invoke Maven goals from a terminal in your project root. If you are in a Linux or macOS environment, you should be able to execute the wrapper by running ./mvnw. If you are on Windows, you can execute the Wrapper by running ./mvnw from a PowerShell terminal, or mvnw from a standard cmd.exe terminal. Now that we’ve seen the provided Maven Wrapper setup, let’s focus, examine the Maven project configuration more closely, and analyze what each section accomplishes.

Maven project (pom.xml)

The Maven project is defined in the Project Object Model pom.xml file. This XML file is the main unit of work for Maven and collects all the information and configuration details that will be used by Maven to build the project.

Let’s examine some of the sections of the pom.xml file that were bootstrapped for us from the Quarkus website.

Maven coordinates (GAV)

The Maven coordinates, also known as GAV, are the minimum required references for a project. These are the groupId, artifactId, and version fields that we defined in the web-based wizard when we bootstrapped the project:

<groupId>com.example.fullstack</groupId>
<artifactId>reactive</artifactId>
<version>1.0.0-SNAPSHOT</version>

These fields act as a unique identifier for the project and enable you to reference it in other projects just like a coordinate system.

Maven properties

The project comes with a set of predefined properties in an XML <properties> block. The following are the most important:

  • maven.compiler.release
    <maven.compiler.release>17</maven.compiler.release>

This property sets the Java version for the project. In this case, both the sources and target classes will require a Java 17 version. This property is used by the Maven Compiler Plugin, and it was introduced in version 3.6 of the plugin. This property relies on the other compiler-plugin.version property, which you shouldn’t change – or at least make sure it’s always later than 3.6.

  • quarkus.platform.version
    <quarkus.platform.version>2.10.2.Final</quarkus.platform.version>

This property specifies the Quarkus version in use. Whenever a new Quarkus version is released, this is the property that you should update to upgrade your project. For patch versions and non-breaking releases, this change should be enough. For other version updates, you might need to change some parts of your code too.

Dependency management

The pom.xml file contains a dependency management block with the following content:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>${quarkus.platform.group-id}</groupId>
      <artifactId>${quarkus.platform.artifact-id}
      </artifactId>
      <version>${quarkus.platform.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

This definition is important to set the version of the Quarkus extension dependencies. It’s using placeholders for the following Maven properties found in the properties section to reference the effective dependency:

<quarkus.platform.artifact-id>quarkus-bom
</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform
</quarkus.platform.group-id>

Under the hood, Maven is copying the dependency management section of the io.quarkus.platform:quarkus-bom, Quarkus’ Bill of Materials (BOM), artifact to the current project. This process enforces the use of a consistent version for all of the provided Quarkus extensions that we’ll see in the next section, Dependencies.

Dependencies

The following block in the project object model is the dependencies definition. These are the actual library dependencies of our project. Let’s see what each of the bootstrapped dependencies does.

RESTEasy Reactive

In this book, we are going to explore the new reactive capabilities of Quarkus. RESTEasy Reactive is a Quarkus-specific implementation of the JAX-RS specification based on Vert.x. It takes full advantage of Quarkus’ reactive non-blocking capabilities, which improve the overall application performance. The following code snippet defines the dependency for this library:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>

JAX-RS is a Java EE or Jakarta EE API specification that enables the implementation of REST web services. It provides common annotations such as @Path, @GET, and @POST, which can be used to annotate classes and methods to implement HTTP endpoints. If you’ve dealt with J2EE, Java EE, or Jakarta EE before, you might already be familiar with these annotations.

This highlights one of the main advantages of Quarkus. The learning curve is very gentle since most of it is based on proven community standards and libraries.

Quarkus ArC

Quarkus ArC is the dependency injection solution provided by Quarkus. It is based on the Java EE CDI 2.0 specification – again, a proven, long-lived standard. The following code snippet specifies this dependency:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-arc</artifactId>
</dependency>

One of the advantages of ArC, and most Quarkus extensions in general, is that it’s build-time oriented. Most analysis and optimizations happen at build time, so none of this processing needs to be performed during the application startup. The result is an application that starts up nearly instantly.

Quarkus JUnit5

Quarkus JUnit5 is the main dependency for the Quarkus testing framework. It provides the @QuarkusTest annotation, which is the main entry point for the test framework. The next code snippet configures this dependency:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-junit5</artifactId>
  <scope>test</scope>
</dependency>

We’ll examine this dependency and its features in more detail in Chapter 5, Testing Your Backend.

Rest Assured

Rest Assured is the last test dependency that was bootstrapped in the project. Although it’s not provided by Quarkus, it’s the recommended way to test its endpoints. The following code snippet is used to define this dependency; notice the groupId value is not io.quarkus anymore:

<dependency>
  <groupId>io.rest-assured</groupId>
  <artifactId>rest-assured</artifactId>
  <scope>test</scope>
</dependency>

We’ll be using it to create the integration tests for our application.

Plugins

Along with the more common Maven plugins, the build plugins section contains an entry for the Quarkus Maven plugin. This plugin provides Maven goals for most of the Quarkus features. Whenever we invoke any Maven command with a quarkus: prefix, this is the plugin that will be responsible for the execution.

Profiles

The last section in the pom.xml file is the one dedicated to profiles. The bootstrapped project contains a single profile with the native ID. We can activate this profile either by using the Maven profile selection flag, -Pnative, or by providing a -Dnative system property (see the activation configuration):

Figure 1.4 – A screenshot of the beginning of the profiles section in pom.xml

Figure 1.4 – A screenshot of the beginning of the profiles section in pom.xml

The profile provides some specific configurations to run tests that partially override the one provided in the build or plugins section. However, the most important part is the quarkus.package.type property. This is the property that instructs Quarkus to build a native binary for our platform. When we package our application with this profile (./mvnw clean package -Pnative), we’ll get a binary file instead of a standard Java archive (JAR) package.

We’ll explore profiles in more detail in Chapter 6, Building a Native Image.

Source files

The bootstrapped project has the regular Java project structure. In addition to the pom.xml project file in the root directory, you will find a src subdirectory that contains the project sources.

Application properties

The application.properties file is located in the src/main/resources directory. This file contains the main configuration for our project. We’ll be modifying the application configuration and behavior by adding entries to it.

Under the hood, Quarkus uses SmallRye Config, which is an implementation of the Eclipse MicroProfile Configuration feature spec. This is another of the battle-tested standards on which Quarkus is based.

This is a standard property file. Each entry is added in a new line. For each line, the config key and the config value are separated by an = sign.

For example, the code to set the application server port would be as follows:

quarkus.http.port=8082

The application.properties file can also be used to define values that can be injected into your application.

Let’s say you defined the following property:

publisher.name=Packt

You could inject the preceding property into your application using the following snippet:

@ConfigProperty(name = "publisher.name")
String publisherName;

Profiles

Quarkus provides the option to build and execute the application based on different profile configurations. Depending on the target environment, you might want to select a specific profile that provides a valid configuration for that environment.

Quarkus has three profiles – dev, which is activated in development mode, test, which is activated when the tests are executed, and prod, which is the default profile when the others don’t apply.

The same file is used for all profiles; to configure an option for a specific profile, you need to prefix the configuration key with % and the profile name, except for prod, which is the default profile and doesn’t require a prefix.

For the previous server port example, we can set the server port in dev mode as follows:

%dev.quarkus.http.port=8082

In general, we’ll be adding configuration for prod, and provide overrides for dev mode when needed.

Static resources

The project contains an index.html file in the src/main/resources/META-INF/resources directory. This file will be automatically served from the underlying application’s HTTP server. When pointing a browser to the root path of the application (http://localhost:8080), you will be greeted with this landing page that was bootstrapped for us:

Figure 1.5 – A screenshot of a browser pointing to http://localhost:8080

Figure 1.5 – A screenshot of a browser pointing to http://localhost:8080

By default, Quarkus will serve any static file that is located in this directory. However, for our application, we’ll be using an alternative method since this approach is not compatible with frontend routing. In Chapter 11, Quarkus Integration, I’ll show you how to implement an API gateway that will be used as an alternative to serving the static resources.

Java code

The bootstrapped project contains some sample code. A GreetingResource class is located in the standard src/main/java directory under the com.example.fullstack package. You will also find two tests for this class in the src/main/test directory under the same package: GreetingResourceTest and GreetingResourceIT. We will place the new code that we implement in the same root package grouped by features.

Docker files

The project contains some example Docker files in src/main/docker. These files can be used to create container images for your application. In Chapter 12, Deploying Your Application to Kubernetes, I’ll show you how to create container images for the application. However, we’ll be using Eclipse JKube, which requires a simpler configuration and doesn’t need these Docker files. JKube is a Maven plugin that generates all of the required configurations for your application to be able to deploy it to Kubernetes; for this reason, it’s not necessary to keep extra configuration files such as Docker or Kubernetes YAML files.

Now that we’ve seen the files and directory structure of the bootstrapped project, let us see how to perform the basic tasks that we will need to develop new features and deploy and run the application.

Development mode

For years, one of the main pain points for Java developers has been the lack of or very little support for hot reloading or live reloading. Traditionally, when you made some changes to your code, you had to recompile the application, package it, and redeploy it. This process was something that could take anywhere from a few seconds to several minutes or even hours in the worst cases. This is usually one of the disadvantages cited when people compare Java to other programming languages.

One of Quarkus’ main goals is bringing joy back to developers, so, naturally, this was one of the priority points to address. Quarkus development mode runs your application and monitors your code. Whenever you change any of the Java application source or resource files, Quarkus detects these changes and performs a hot deployment. You just need to refresh your browser for the changes to take effect.

We can start the development mode by running the quarkus:dev Maven goal from the project’s root directory as follows:

./mvnw quarkus:dev

You will see the following result:

Figure 1.6 – A screenshot of the IntelliJ terminal running Quarkus development mode

Figure 1.6 – A screenshot of the IntelliJ terminal running Quarkus development mode

If you check the preceding messages, you’ll notice that Quarkus automatically selected for us the dev profile and started the live coding mode.

We can now point our browser to the URL of the sample endpoint that was bootstrapped (http://localhost:8080/hello). If everything went well, the browser will show the Hello RESTEasy Reactive message:

Figure 1.7 – A screenshot of the browser pointing to the hello endpoint

Figure 1.7 – A screenshot of the browser pointing to the hello endpoint

If we open the GreetingResource class in our IDE, we should be able to see the definition for this endpoint. We can change the greeting message to something else:

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
    return "Hello Quarkus live coding!";
}

In the traditional Java world, we would now need to recompile and redeploy the application to be able to see the changes. However, if we reload the browser window, our modified message should be visible.

Debugging in development mode

If you check the messages in Figure 1.6 closely, you’ll notice that Quarkus has also enabled a remote debugging port:

Listening for transport dt_socket at address: 5005

This means we can easily start a debug session from IntelliJ IDEA. For this, we need to create a new debug configuration from the Run > Edit Configurations… menu:

Figure 1.8 – A screenshot of the IntelliJ IDEA Run menu

Figure 1.8 – A screenshot of the IntelliJ IDEA Run menu

From the Run/Debug Configurations screen, we need to create a new Remote JVM Debug configuration. The default options should be fine for Quarkus, so we only need to specify a name for this configuration:

Figure 1.9 – A screenshot of IntelliJ IDEA Quarkus debug configuration

Figure 1.9 – A screenshot of IntelliJ IDEA Quarkus debug configuration

Once we save the configuration, we can run it and should be able to set a breakpoint on our endpoint definition. If we reload the browser window, the debugger should stop at our breakpoint.

When combined with easy debugging, live reloading is very powerful and will certainly improve our developer performance. Now that we know how to use the Quarkus development mode to implement and debug our code, let us see how to run the tests for our application.

Continuous testing

One of Quarkus 2.X’s features is its ability to run tests continuously. This is a feature borrowed from other programming languages, such as Ruby, that have offered it for a long time. It is also a further step in achieving Quarkus’s goal to bring back developer joy to Java.

For users who practice test-driven development (TDD), this will massively improve their development cycle performance. In a usual TDD process, developers first write a test for a feature and then implement the code that will make that test pass. This process is repeated for each of the properties of the feature and for each code refactor. Continuous test execution provides instant feedback and allows the developer to concentrate and focus on the implementation and not on the process.

When Quarkus is run in continuous testing mode, it will detect code changes to both code and tests. For each change it detects, it will re-run the relevant tests for the affected code.

Just as with the development mode, we can run a Maven command to start the continuous testing mode as follows:

./mvnw quarkus:test

If you recall, in the Development mode section, we changed the greeting in the GreetingResource class but we didn’t change the test. The first thing we’ll see once we invoke the quarkus:test Maven goal is a test failure:

Figure 1.10 – A screenshot of quarkus:test failing to be invoked

Figure 1.10 – A screenshot of quarkus:test failing to be invoked

We can now open GreetingResourceTest and update the expected response body to the new greeting, Hello Quarkus live coding!:

@Test
public void testHelloEndpoint() {
    given()
      .when().get("/hello")
      .then()
         .statusCode(200)
         .body(is("Hello Quarkus live coding!"));
}

If we save the changes, the test should automatically re-run and it will be green again:

Figure 1.11 – A screenshot of quarkus:test passing invocation

Figure 1.11 – A screenshot of quarkus:test passing invocation

Expanding on the TDD use case, if there was a new requirement to expose a /hello/world endpoint, the first step would be to add a new test:

@Test
public void testHelloWorldEndpoint() {
    given()
      .when().get("/hello/world")
      .then()
      .statusCode(200)
      .body(is("Hello world!"));
}

There is no implementation yet, so the execution would fail. We could then implement the new endpoint to make the test pass as follows:

@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/world")
public String helloWorld() {
    return "Hello world!";
}

Once tests pass, the next step could be to retrieve the endpoint value from an external service. So, we would modify the test, then the implementation, and start the cycle again. It should be clear now how the experience of the overall process is notably improved by continuous testing.

TDD ensures that the features defined in the provided specs are working using unit tests. This allows you to write code with fewer bugs and spend less time on long debugging sessions trying to fix errors. Now that we’ve seen how to perform TDD in Quarkus, let us see how to package the application for its distribution.

Packaging the application

The final step to being able to distribute and run the application would be to package it. Besides the native mode, which we already analyzed in the Profiles section, Quarkus offers the following package types:

  • fast-jar: This is the default packaging mode. It creates a highly optimized runner JAR package, along with a directory and its dependencies.
  • uber-jar: This mode will generate a fat JAR containing all of the required dependencies. This JAR package is suitable for distribution of the application on its own.
  • native: This mode uses GraalVM to package your application into a single native binary executable file for your platform.
  • native-sources: This type is intended for advanced users. It generates the files that will be needed by GraalVM to create the native image binary. It’s like the native packaging type but stops before triggering the actual GraalVM invocation. This allows performing the GraalVM invocation in a separate step, which might be useful for CI/CD pipelines.

You can control the packaging mode by setting the quarkus.package.type Maven property. You can set this property in the pom.xml properties section or via the command line when running the Maven commands:

./mvnw -D"quarkus.package.type=uber-jar" clean package

For the moment, we’ll be using the default packaging mode. You can package the application running the following command:

./mvnw clean package

If everything went well, you should now be able to run the application by executing the following:

java -jar target/quarkus-app/quarkus-run.jar

You should now be able to navigate to http://localhost:8080 or any of the HTTP endpoints we created in the previous steps.