How to Set up an Application Using Quarkus and React

Georgios Petrou
arconsis
Published in
5 min readSep 7, 2023

This article is the first part of a series about an enterprise architecture paradigm for building applications using Quarkus, React, Backend for frontend (BFF) and monorepos. The idea is to have one (or more) backend services and multiple BFFs, each containing a React application (or other client applications), and the ability to build, test and deploy all services together. The React application is built and served by the Quarkus app, which among other benefits makes it easier to scale and provides a unified deployment and management experience. More on the pros and cons of the patterns and architecture we are using will follow in the next articles.

In this first part, I’ll show you in detail how to setup your project and run it locally. What you will have created by the end of this article:

  • A Quarkus backend service
  • A Quarkus BFF service
  • A React web application served by the BFF service

Project structure

To get started, let’s create our project structure. In order to execute these commands you should install the Quarkus CLI. You can find information on how to install it here.

From a CLI, copy and paste these commands and press Enter (you can copy/paste everything at once):

quarkus create app quarkus-react-mono-repo-example --extensions=kotlin --gradle-kotlin-dsl 
cd quarkus-react-mono-repo-example
quarkus create app quarkus-react-mono-repo-example:backend-service --extension=kotlin,resteasy-reactive,resteasy-reactive-jackson --gradle-kotlin-dsl
quarkus create app quarkus-react-mono-repo-example:bff-service --extension=kotlin,resteasy-reactive,resteasy-reactive-jackson --gradle-kotlin-dsl

On the first command, we generate a Quarkus project. In reality, we don’t need it to be a Quarkus project. This is done in order to generate the necessary files easily and quickly.

Now we have to delete some folders we don’t need. You can delete the src folder from the root project and then from each subproject, keeping only the src folder and build.gradle.kts file.

To create the React web application, navigate to bff-service/src/main and run the following command from the CLI:

npx create-react-app webapp --template typescript

Your folder structure should look like this for now:

Quarkus services configuration

On the root’s folder gradle.properties, we should then add the versions for Quarkus, Kotlin and Node that will be shared in the whole project. Add this to the file:

# Quarkus
quarkusPluginId=io.quarkus
quarkusPluginVersion=3.2.3.Final
quarkusPlatformGroupId=io.quarkus.platform
quarkusPlatformArtifactId=quarkus-bom
quarkusPlatformVersion=3.2.3.Final
#
# Node
nodePluginId=com.github.node-gradle.node
nodePluginVersion=5.0.0
nodeVersion=18.16.0
#
# Kotlin
kotlinVersion=1.8.21
kotlin.code.style=official

Now on the root’s folder navigate to gradle -> wrapper -> gradle-wrapper.properties file and on the distributionUrl property add the following line: https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip. Your gradle-wrapper.properties file should now look like this:

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Next, let’s update settings.gradle.kts file in the root folder with the tools and versions we declared above. Your settings.gradle.kts file should look like this:

pluginManagement {
val quarkusPluginVersion: String by settings
val quarkusPluginId: String by settings
val nodePluginVersion: String by settings
val nodePluginId: String by settings

val kotlinVersion: String by settings

repositories {
mavenCentral()
gradlePluginPortal()
mavenLocal()
}
plugins {
kotlin("jvm") version kotlinVersion
kotlin("plugin.allopen") version kotlinVersion
kotlin("plugin.noarg") version kotlinVersion

id(quarkusPluginId) version quarkusPluginVersion
id(nodePluginId) version nodePluginVersion
}
}
rootProject.name="quarkus-multi-project"
include("bff-service", "backend-service")

Now that we have the versions in the settings.gradle.kts file, we need to edit the build.gradle.kts files in order to get the versions from it.

Root build.gradle.kts file:

group = "org.acme"
version = "1.0.0-SNAPSHOT"

plugins {
kotlin("jvm") apply false
}

subprojects {
repositories {
mavenCentral()
mavenLocal()
}

tasks.withType<JavaCompile>().configureEach {
sourceCompatibility = JavaVersion.VERSION_17.toString()
targetCompatibility = JavaVersion.VERSION_17.toString()
}
}

Backend-service and Bff-service build.gradle.kts files:

plugins {
kotlin("jvm")
kotlin("plugin.allopen")
id("io.quarkus")
id("com.github.node-gradle.node")
}

val quarkusPlatformGroupId: String by project
val quarkusPlatformArtifactId: String by project
val quarkusPlatformVersion: String by project
val nodeVersion: String by project

dependencies {
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
implementation("io.quarkus:quarkus-resteasy-reactive")
implementation("io.quarkus:quarkus-kotlin")
implementation("io.quarkus:quarkus-resteasy-reactive-jackson")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("io.quarkus:quarkus-arc")
testImplementation("io.quarkus:quarkus-junit5")
testImplementation("io.rest-assured:rest-assured")
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString()
kotlinOptions.javaParameters = true
}

When we run the assemble command (./gradlew assemble ), both services will be built.

React app configuration

To serve the React application on the bff-service, we need to add some npm tasks to the build.gradle.kts file on the bff-service. Add these to the above file:

node {
version.set(nodeVersion)
download.set(true)
workDir.set(file("${project.rootDir}/.cache/nodejs"))
nodeProjectDir.set(file("${project.projectDir}/src/main/webapp"))
}

tasks.register<com.github.gradle.node.npm.task.NpmTask>("buildReact") {
dependsOn(tasks.npmInstall)
npmCommand.set(listOf("run", "build"))
environment.set(mapOf("BUILD_PATH" to "${buildDir}/resources/main/META-INF/resources"))

inputs.files(fileTree("${project.projectDir}/src/main/webapp") {
exclude("node_modules")
})
outputs.dir("${buildDir}/resources/main/META-INF/resources")
}


tasks.register<com.github.gradle.node.npm.task.NpmTask>("startReact") {
dependsOn(tasks.npmInstall)
npmCommand.set(listOf("run", "start"))

inputs.dir("${project.projectDir}/src/main/webapp")
}

tasks.getByName("processResources").dependsOn(tasks.getByName("buildReact"))

Here we build react and add it to the desired destination and also execute the npm run startcommand to start the react application. We also need to delete the META-INF folder that is in the bff-service service/src/main/resources folder. This way, we will be serving the react application and not the default static file generated by Quarkus.

Run the project

Now you can run the bff-service with the quarkus dev command. If you navigate to http://localhost:8080/ in which the backend service is served, you will see the Create React App landing page which is served by Quarkus. For easier development, you can navigate to the webapp directory and run npm run start to develop the React application.

That’s it for now. In the next parts, we‘ll talk more about where this way of creating applications fits into the enterprise architecture environment, how we can automatically build and test the application using continuous integration practises, and also how we can deploy everything using Kubernetes. You can find the project here.

Thanks for reading.

--

--