Serving React apps from Spring Boot

Photo background by Samuel Scrimshaw

Indrek Ots
by Indrek Ots
5 min read

Categories

  • articles

Tags

  • react
  • web
  • spring
  • spring boot
  • java
  • gradle

Let’s say you’ve decided to create a web app with React. You also want to build a back end so you’ve picked Spring Boot to do the job. There are lots of ways to serve a React app in production. In this post, we’re going to take a look at how to bundle the React app inside a Spring Boot back end.

Create a Spring Boot app

To get started, go to start.spring.io and generate yourself a new Spring Boot app (or use an existing one if you wish). Make sure you at least pick the Web dependency. In this example, I’m using Gradle as my build tool. You should have the following folder structure (or similar):

.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── demo
    │   │               └── DemoApplication.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── com
                └── example
                    └── demo
                        └── DemoApplicationTests.java

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 can’t just put our React source files to src/main/resources and expect everything to work.

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

Create a React app

It’s 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

You’re free to choose where you would like to place the generated web app. In this example, I’m going to copy it to src/main/webapp.

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. I’m using Gradle in this example 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, I’m using the gradle-node-plugin. The following are the most interesting parts of my Gradle build script.

// 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, let’s 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 don’t 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.

Summary

We went through how to create a basic React web app and serve it with Spring Boot. The key is to configure our Gradle build script so that in addition to building and packaging our Java code, we also build and minify the web app and copy it to the Jar file. Essentially, the same principles apply if you’re using any other build tool (e.g. Maven) or you want to use something else than React (e.g. Angular).

The example code presented in this post can be found on Github.