1 Serve ReactApp with SpringBoot
djmil edited this page 2023-08-13 15:46:25 +02:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Create a Spring Boot app

To get started, go to start.spring.io and generate a new Spring Boot app. Make sure to pick at least the Web dependency. We are going to use Gradle as build tool.

Serving static content with Spring Boot

To serve our front end web app from a Spring Boot jar file, we need to first understand how Spring Boot handles static content.

By default, Spring Boot serves static content from a directory called /static (or /public or /resources or /META-INF/resources) in the classpath or from the root of the ServletContext

For example, the following is an example index.html that we will put into src/main/resources/static/ folder.

<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Static content test</title>
</head>
<body>
    <h1>Hello World</h1>
</body>
</html>

When we start our Spring Boot app and point our web browser to it (with default configuration the URL should be localhost:8080), Hello World should be displayed to us. Everything in src/main/resources/static/ is moved to the correct location in the packaged jar file. Now that we know how to serve static content, it should be relatively easy to serve the React app in a similar manner.

But wait! Javascript source files have to be built first, CSS minified etc. We cant just put our React source files to src/main/resources and expect everything to work.

Thats correct. We have to build the React app before we can move it into the /static directory. Lets have a look at how to do that. But first, we need to generate a new React app.

Create a React app

Its super easy to get started with React by using create-react-app. If you have the necessary tools installed (e.g. node, npm), you can just execute this command

npx create-react-app enter-app-name-here

It does not meter where exactly to place the generated web app. This projec going to use src/main/webapp path.

Gradle build script

Before we can put our web app to production, we must create a minified bundle with npm run build. To serve the minified bundle with Spring Boot, we have to move it to one of the directories where Spring Boot serves static content. This project is using Gradle to build and package the Spring Boot application, but the same can be achieved with other build tools. The key is to remember that in addition to building the Java code, we must also create the minified bundle of our web app and copy it to the correct directory.

To integrate a Gradle build with Node, the gradle-node-plugin can be used.

[!TODO] The following are the most interesting parts of a Gradle build script from outdated gradle-node-plugin plugin

plugins {
  id "com.moowork.node" version "1.3.1"
}

....

// Read more about how to configure the plugin from
// https://github.com/srs/gradle-node-plugin/blob/master/docs/node.md
node {
  download = true

  // Set the work directory for unpacking node
  workDir = file("${project.buildDir}/nodejs")

  // Set the work directory for NPM
  npmWorkDir = file("${project.buildDir}/npm")
}

task appNpmInstall(type: NpmTask) {
  description = "Installs all dependencies from package.json"
  workingDir = file("${project.projectDir}/src/main/webapp")
  args = ["install"]
}

task appNpmBuild(type: NpmTask) {
  description = "Builds production version of the webapp"
  workingDir = file("${project.projectDir}/src/main/webapp")
  args = ["run", "build"]
}

task copyWebApp(type: Copy) {
  from 'src/main/webapp/build'
  into 'build/resources/main/static/.'
}

appNpmInstall is a Gradle task that runs npm install in the webapp directory. Similar to appNpmInstall, the build script declares the appNpmBuild task that runs npm run build to create the minified bundle of the web app. Finally, copyWebApp is a simple Copy task to copy the minified bundle to the static content directory. Feel free to test run these tasks in isolation and see how they work. For example, running gradle appNpmInstall should install all of our web app dependencies and place them in src/main/webapp/node_modules/ directory.

Development flow

During web app development, you should start the web app in development mode. Therefore, instead of bundling the web app inside the Spring app, lets serve it separately using the dev server. Go to the webapp directory and run npm start. This way, your web app will reload automatically if you make any changes in the source files. At the same time, you should also start the Spring Boot application.

To make the development flow with Spring Boot a little more pleasant, you can also configure spring-boot-devtools and enable automatic restarts. When configured, the Spring Boot app restarts whenever files on the classpath change.

Build for production

To build our application for production, in addition to compiling and packaging our Java code, we must also create a minified bundle of the web app and place it inside the Jar file to one of the directories where Spring Boot serves static content. We already have our build script configured with tasks that can install our dependencies, create the minified bundle and copy it to the correct location. To make the packaging a bit more simpler, we could define some dependencies between the build tasks in our build script.

appNpmBuild.dependsOn appNpmInstall
copyWebApp.dependsOn appNpmBuild
compileJava.dependsOn copyWebApp

This makes sure that whenever Java code is compiled, web app dependencies are also installed, minified bundle is created and the bundle is copied to a static content directory. When you run gradle clean build, you dont have explicitly run any web app specific Gradle tasks. Once the build has finished, you can start the application via command-line.

java -jar path/to/web-app.jar

Point your browser to it and you should see the newly created React app running.