Spring Boot and React
Part 1: Build Setup with Gradle and Webpack
(Updated: )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.
- Part 1: Setting up a build with Gradle and Webpack
- Part 2: Pulling Data into React from Spring Boot APIs
This article is targeting the following products and versions:
Java | 11 |
---|---|
Spring Boot | 2.4.x |
Gradle | 6.8.x |
React | 17.x |
Webpack | 5.x |
Node | 14.x |
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.4.2'
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.4.2'
id 'java'
id "com.github.node-gradle.node" version "3.0.1"
}
apply plugin: 'io.spring.dependency-management'
group = 'com.andrew_flower.demo'
version = '1.0.0'
sourceCompatibility = '11'
node {
version = '14.15.5'
}
// ...
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
version.
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 build 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.12.13",
"@babel/preset-env": "^7.12.13",
"@babel/preset-react": "^7.12.13",
"babel-loader": "^8.2.2",
"react": "~17.0.1",
"react-dom": "~17.0.1",
"webpack": "^5.21.2",
"webpack-cli": "^4.5.0"
}
}
Next, we create configuration for Webpack, webpack.config.js
, in the root directory.
module.exports = {
devtool: 'source-map',
output: {
filename: 'react-app.js'
},
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. Also note that we've specified
the output file name react-app.js
, which was referenced in the index.hml
.
At this point if you run the following shell command, it will generate our bundled app
$ node_modules/webpack/bin/webpack.js --entry ./src/main/webapp/javascript/main.jsx -o ./src/main/resources/static/dist
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.4.2'
id 'java'
id "com.github.node-gradle.node" version "3.0.1"
}
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'
}
node {
version = '14.15.5'
}
task buildReactApp(type: NodeTask, dependsOn: 'npmInstall') {
script = project.file('node_modules/webpack/bin/webpack.js')
args = [
'--mode', 'development',
'--entry', './src/main/webapp/javascript/Main.jsx',
'-o', './src/main/resources/static/dist'
]
}
processResources.dependsOn 'buildReactApp'
clean.delete << file('node_modules')
clean.delete << file('src/main/resources/static/dist')
On lines 25-32 we add a new Gradle task that invokes Webpack. 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 directory 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, by specifying additional
--entry
arguments.
The mode configuration on line 28 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 34 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 35-36, 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/,
use: ['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
.
...
"css-loader": "^5.0.2",
"style-loader": "^2.0.0",
...
Note that you could also add dependencies using the npm
command line.
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:
- build.gradle: Java dependencies and overall build configuration
- package.json: Javascript dependencies
- 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.
- Part 1: Setting up a build with Gradle and Webpack
- Part 2: Pulling Data into React from Spring Boot APIs
References
Bitcoin
Zap me some sats
This blog post itself is licensed under a Creative Commons Attribution 4.0 International License. However, the code itself may be adapted and used freely.