andrew Flower

Spring Boot and React

Part 1: Build Setup with Gradle and Webpack

The goal is of this article is to demonstrate how to build an application with a Spring Boot backend and React frontend, using Gradle as the master build tool.

This blog post is targeting anyone who is trying to build an app composed of Spring Boot and React and primarily for those coming from the Java and Spring world, like myself. A goal of this approach is to remove any need to use NodeJS or NPM directly, and rather let Gradle handle that. This is made possible by a very useful gradle-node-plugin.

This blog is part 1 of a series I'm doing on Spring and React.

This article is targeting the following products and versions:

Java 11
Spring Boot 2.2.6
Gradle 5.5.1
React 16.13
Node 12.16

The accompanying source code is available here:

The Individual Parts

Breaking it down into three sections, we'll first make a basic Spring Boot Application and then see how to integrate a Javascript application built with the React framework.

Basic Spring Boot Application

To create a new Spring Boot project I use IntelliJ, but you can easily generate a project using the online Spring Initializr or, if you really want, do it manually. The only dependency we'll need for now is Spring Web, so add the Spring Boot Web Starter.

After generating the project we should have a gradle file that looks like this:

plugins {
    id 'org.springframework.boot' version '2.2.6.RELEASE'
    id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.andrew_flower.demo'
version = '1.0.0'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
}

And we if we run this with ./gradlew bootRun, we can open http://localhost:8080/ in the browser, but will see a 404 for now (Whitelabel Error Page).

A Static Landing Page

Because our focus in this blog is on React, all that is left to do in the Spring app is create a static HTML page that will house the React application. Spring Boot auto-configures Spring Web MVC to look for a static index.html in one of the configured static resource locations .

So, we add a index.html file to our project at /src/main/resources/static/index.html that looks like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>React on Spring</title>
  </head>
  <body>
    <div id="react-mountpoint"></div>
    <script src="dist/react-app.js"></script>
  </body>
</html>

Now opening our application in the browser should at least give us the blank white page. Of course we are missing the referenced Javascript file dist/react-app.js on line 9. This will come from the output of our React/JS build later. Also, note the #react-mountpoint on line 8, where we want to insert our React app. This id must match what you specify to React.

Gradle with Node

Switching gears away from Spring now, it's time to look at building our React App. React uses its own language called JSX, and therefore needs to be compiled into regular Javascript that a browser's runtime can interpret.

The way we do this is to precompile all our React JSX into Javascript at build time and bundle it as part of our application. We need a Javascript Runtime to do this, and this is where Node.js comes in. Node.js or Node, is a very versatile JS runtime, but we will simply use it as part of our build pipeline.

Gradle Node Plugin

Because we are using Gradle as our build tool, we want to invoke Node from Gradle to do the Javascript building. There is a very useful gradle-node-plugin that we can use in the build.gradle file by declaring and applying the plugin com.moowork.node. This plugin provides a few Node-specific functions that are useful to us.

Firstly, one of the tasks provided by the plugin (you can see them with ./gradlew tasks), is the npmInstall task. This uses Node's Package Manager (NPM), to install all depedencies listed in a package.json file.

Secondly, it provides a new Gradle task type called NodeTask, which allows one to run javascript using Node.

plugins {
    id 'org.springframework.boot' version '2.2.6.RELEASE'
    id 'java'
    id "com.moowork.node" version "1.3.1"
}

apply plugin: 'io.spring.dependency-management'
apply plugin: "com.moowork.node"

group = 'com.andrew_flower.demo'
version = '1.0.0'
sourceCompatibility = '11'

node {
    version = '12.16.2'
    npmVersion = '6.14.4'
    download = true
}

// ...

Above you can see the only two lines required to start using Node in Gradle. It is optional to include the node {} with specific node and npm versions. This doesn't actually include any Node tasks in our build pipeline yet however.

Node Dependency Management

When NPM installs modules, they are stored in a sub-directory called node_modules. The modules that are required by a node package are defined in the package.json file which, in a normal Node project, defines all the project metadata, but we are primarily concerned about the dependency list. Once NPM has installed dependencies, it outputs exactly what version of each dependency was installed in the package-lock.json file.

Initially we must create a blank package-lock.json file in the root directory. Then we can create our package.json file like below:

{
  "name": "spring-react-app",
  "description": "Sample application using React with Spring Boot",
  "dependencies": {}
}

Building a React App

This section will focus on the final step - Building a React Component. We will first create the JSX code to display a simple component and then show how to integrate it into our Gradle buile so that it is shown in the HTML served by the Spring Boot Application.

A Basic Component

Let's create a super simple React Component for the example:

import React, { Component } from "react";
import ReactDOM from 'react-dom';

class Main extends Component {
    render() {
        return (
            <div>
                <h1>Demo Component</h1>
                <img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg"/>
            </div>
        );
    }
}

ReactDOM.render(
    <Main />,
    document.getElementById('react-mountpoint')
);

Our component, Main is just a header element followed by an image sourcing the React logo. Note that we specified the id of the <div> field, #react-mountpoint that we want to mount our component at in index.html.

Introducing Webpack to our Build

We now need to convert this JSX file into regular JS, such that a Javascript runtime can interpret it. Babel is a well established Javascript compiler, that can convert all sorts of resources to Javascript, using different types of loaders. In our case we are converting React JSX code. We also want to potentially bundle multiple files into a single Javascript file. This is where Webpack comes in. Another well established tool for bundling assets.

We will use Webpack to send our JSX files for processing with Babel and then bundle them into a single JS file for our web application to serve. Let's add the dependencies to the package.json file so that NPM will load them for us.

{
  "name": "spring-react-app",
  "description": "Sample application using React with Spring Boot",
  "dependencies": {
    "@babel/core": "^7.9.0",
    "@babel/preset-env": "^7.9.0",
    "@babel/preset-react": "^7.9.0",
    "babel-loader": "^8.1.0",
    "react": "~16.13.1",
    "react-dom": "~16.13.1",
    "webpack": "^4.42.1",
    "webpack-cli": "^3.3.11"
  }
}

Next, we create configuration for Webpack, webpack.config.js, in the root directory.

module.exports = {
    devtool: 'source-map',
    module: {
        rules: [{
            test: /\.(js|jsx)$/,
            exclude: /node_modules/,
            loader: "babel-loader",
            options: {
                presets: ['@babel/preset-env', '@babel/preset-react']
            }
        }]
    },
    resolve: {
        extensions: ['.js', '.jsx']
    }
};

This configuration is primarily telling webpack to also process files having .jsx (in addition to .js) extensions using the babel-loader. It also configures the babel-loader to interpret the files as React files. The Babel presets configuration can also be done in a .babelrc file, but we've chosen to do it here.

At this point if you run the following shell command, it will generate our bundled app

$ node_modules/.bin/webpack --entry ./src/main/webapp/javascript/main.jsx -o ./src/main/resources/static/dist/react-app.js

You could even open the index.html page in the browser directly and see it working, but we want to automate this in the Gradle file, so we will add a build task like below to build.gradle

plugins {
    id 'org.springframework.boot' version '2.2.6.RELEASE'
    id 'java'
    id "com.moowork.node" version "1.3.1"
}

apply plugin: 'io.spring.dependency-management'
apply plugin: "com.moowork.node"

group = 'com.andrew_flower.demo'
version = '1.0.0'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
}

node {
    version = '12.16.2'
    npmVersion = '6.14.4'
    download = true
}

task buildReactApp(type: NodeTask, dependsOn: 'npmInstall') {
    script = project.file('node_modules/.bin/webpack')
    args = [
            '--mode', 'development',
            '--entry', './src/main/webapp/javascript/Main.jsx',
            '-o', './src/main/resources/static/dist/react-app.js'
    ]
}

processResources.dependsOn 'buildReactApp'
clean.delete << file('node_modules')
clean.delete << file('src/main/resources/static/dist')

On lines 22-29 we add a new Gradle task that invokes Webpack to build the react-app.js. Here we specify the entry point to the app. Webpack will assume that all other needed files will be imported by the entry-point. We also specify the output file for the bundle. It's common to include entrypoint and output configuration in the Webpack config file, but for this kind of Project I prefer to centralise as much as possible in the Gradle file. Multiple entry points and outputs are also possible.

The mode configuration on line 25 is probably better configured based on a Gradle Project property such that it depends on something like ./gradlew -Penv=production. But for this blog we'll keep it simple. Webpack's development mode does not minify files and includes sourcemaps.

On line 31 Gradle is instructed to first ensure that the React app is built before processing project resources. processResources is part of the Gradle Java Plugin.

Finally, on lines 32-33, extra locations are added to the deletion step for ./gradlew clean. Running the application with ./gradlew bootRun and navigating to http://localhost:8080/, we can see our App running!

Right now there is no interaction between the React App and the Spring Server, so it could be served completely statically, but for demo purposes this is how to set up the foundation for more sophisticated apps.

Adding Style

For educational purposes, let's see how to add style to the app. Of course you could serve CSS statically, but React and Babel allow you to bundle style with the Java application in JS code. First we create our CSS src/main/webapp/css/Main.css that centers our App in a column and gives it a slight background.

#main {
    background: #eff;
    margin: 0 auto;
    width: 800px;
}

Then we include this in our JSX file and give our component an id:

//.....
import '../css/main.css';

class Main extends Component {
    render() {
        return (
            <div id="main">
                <h1>Demo Component</h1>
                <img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg"/>
            </div>
        );
    }
//.....

We tell webpack to process CSS files with style and CSS loaders, updating the webpack.config.js. Note that there are also other style loaders available like less-loader.

....
    rules: [{
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
            presets: ['@babel/preset-env', '@babel/preset-react']
        }
    }, {
        test: /\.css$/,
        exclude: /node_modules/,
        loader: "style-loader!css-loader"
    }]
....

Finally we need to instruct NPM to install these loaders so that Webpack can use them, so we add to the package.json:

...
    "babel-loader": "^8.1.0",
    "css-loader": "^3.5.2",
    "style-loader": "^1.1.4",
    "react": "~16.13.1",
...

Running the app again, we can see the styling result. And all of this is bundled in a single JS file thanks to Webpack.

Summary

It was demonstrated how one can create a basic Spring Boot Application with a React JS frontend, using Gradle as the sole build tool. There is no need to manually install Node packages or run Webpack independently - everything is encapsulated in the Gradle build pipeline, with delegation to Node and Webpack to perform the bundling of JSX (and assets) into a single Javascript.

Dependency management for Java and Javascript is handled in two different files. Here are some files with special responsibilities for this type of project:

  1. build.gradle: Java dependencies and overall build configuration
  2. package.json: Javascript dependencies
  3. webpack.config.js: Webpack configuration - what to bundle, and how to do it

The accompanying source code is available here:

Where to next?

This blog is part 1 of a series I'm doing on Spring and React. Check out the next part which will actually show you how to interact between the React Application in Browser and the Spring Application on the Server.

References