Getting Started with React and Spring Boot

Right now, I am trying to deepen my react knowledge and skills. So I started to set up a little project where I can play around: A spring boot backend and a client application written with react and redux. You can get the whole source code on GitHub: dtanzer/example-react-spring-boot. Here is a really detailed guide that describes what I did to make it work (and often why I did it)...

Note: If you just want to play around with what I did, don't repeat all my steps, clone my github project. This guide is just here to explain why the code is how it is right now, and how all the parts play together.

Basic Project Structure

Nowadays I tend to split even the simplest project in multiple modules (I am using the IntelliJ IDEA jargon here) to make it easier to manage dependencies later when they grow. So my basic module structure for this project is:

react-spring-boot
+---common
+---spring-boot-webapp
\---web-frontend

spring-boot-webapp is the actual application. It contains the @SpringBootApplication class ("WebApplication" in this case) and the application.properties. It also has a dependency on all other modules to be able to run the whole application. This allows me to keep dependencies between other modules low, and to even apply the dependency inversion principle for modules.

common is basically a dumping ground for code that all (or most) modules need, and where I didn't find a better module name yet. I'll try to keep the code in this module to a minimum.

web-frontend contains the Spring WebMVC @Controller classes, static files and javascript code.

Later, I will need more modules for REST APIs, backend functionality, and so on. But right now, I just want to get the react app to work, so this is enough (or maybe even over-engineered, as some would say).

A more complete picture of the basic structure is:

react-spring-boot
+---common
+---spring-boot-webapp
|   \---src
|       \---main
|           +---java
|           |   \---net.davidtanzer.reactspringboot
|           |       \---WebApplication.java
|           \---resources
|               \---application.properties
\---web-frontend
    \---src
        +---main
        |   +---java
        |   +---javascript
        |   \---resources
        |       +---static
        |       |   +---css
        |       |   \---js
        |       \---templates
        \---test
            \---javascript

Build With Gradle

I want to build the application with gradle and use the gradle wrapper. This is pretty straight forward. Every module has a build.gradle file, and there is a global build- and settings-file.

react-spring-boot
+---common
|   \---build.gradle
+---spring-boot-webapp
|   \---build.gradle
\---web-frontend
|   \---build.gradle
+---gradlew
+---gradle
|   \---wrapper
+---build.gradle
\---settings.gradle

settings.gradle only lists all the modules for now:

include 'common', 'spring-boot-webapp', 'web-frontend'

rootProject.name = 'react-spring-boot'

build.gradle configures global dependencies (dependencies for all projects and for all build scripts). It is pretty simple but a little long. You can view the whole file on github.

spring-boot-webapp/build.gradle configures the build for the whole application.

buildscript {
    ext {
        springBootVersion = '1.3.0.RELEASE'
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'spring-boot'

jar {
    baseName = 'react-spring-boot'
}

dependencies {
    compile project(':common')
    compile project(':web-frontend')

    compile 'org.springframework.boot:spring-boot-starter-actuator'

    testCompile 'org.springframework.boot:spring-boot-starter-test'
}

web-frontend/build.gradle adds the web dependencies of spring boot:

buildscript {
    ext {
        springBootVersion = '1.3.0.RELEASE'
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'spring-boot'

dependencies {
    compile project(':common')

    compile 'org.springframework.boot:spring-boot-starter'
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
}

But I also want to manage my javascript dependencies with gradle. I have tried several plugins that promise to do this, but none did work for me. client-dependencies-gradle has a bug, for example, where it cannot resolve dependencies that have a circular dependency. So I juss call "npm" from the command line. I have also created a task for executing "browserify" (which packs all javascript resources into a single file):

task clientDependenciesDev(type: Exec) {
    commandLine 'npm', 'install', '--save', 'mocha', 'jsdom', 'react-addons-test-utils', 'chai', 'sinon', 'enzyme'
}

task clientDependencies(type: Exec, dependsOn: 'clientDependenciesDev') {
    commandLine 'npm', 'install', '--save', 'react', 'react-dom', 'babelify', 'babel-core', 'babel-preset-react', 'babel-preset-es2015'
}

task browserify(type: Exec) {
    commandLine 'browserify', '-t', '[', 'babelify', ']', 'src/main/javascript/main.js', '-o', 'src/main/resources/static/js/bundle.js'
}

You can execute one of these tasks from the root project like this:

[david@localhost react-spring-boot]$ ./gradlew web-frontend:browserify
:web-frontend:browserify

BUILD SUCCESSFUL

Total time: 4.373 secs

Frontend / React

We need an index.html as a placeholder to load the react app, and the application itself (written in JavaScript):

web-frontend
\---src
    +---main
    |   +---java
    |   +---javascript
    |   |   +---headerarea.js
    |   |   \---main.js
    |   \---resources
    |       \---static
    |           \---index.html
    \---test
        \---javascript

The static index.html really just loads the react app:

<html>
    <head>
    </head>
    <body>
        <div id="example">Example...</div>

        <script src="js/bundle.js"></script>
    </body>
</html>

main.js initializes the react application:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import {HeaderArea} from './headerarea';

ReactDOM.render(
    <HeaderArea/>,
    document.getElementById('example')
);

And headerarea.js defines our first real component, <HeaderArea>:

import React, { Component } from 'react';

export class HeaderArea extends Component {
    render() {
        return <header>Header...</header>
    }
}

Run the application

web-frontend
+---src
\---.babelrc

For production, we need to bundle all javascript in the single js/bundle.js file, which is loaded from our index.html. So, before running the application, we always have to call:

[david@localhost react-spring-boot]$ ./gradlew web-frontend:browserify

This command will also run babel to translate our ES2016 jsx files to something all browsers understand, so we need a .babelrc:

{
  "presets": ["es2015", "react"]
}

In intelliJ IDEA, we can automate this:

Automatic browserify before build
Automatic browserify before build / run

Now we can start the application in IntelliJ, load it in the browser, and we should see a very simple page that just contains the text "Header...".

Test With Mocha

Now we can add a first unit test for the web frontend. This test does not actually test some of our code yet, it just verifies that we have set up the test system correctly. The tests also need some common setup. I'll also add a shell script to run mocha (the test runner to run our frontend tests):

web-frontend
+---src
|   +---main
|   \---test
|       \---javascript
|           +---environment.spec.js
|           \---setup.js
\---mocha.sh

mocha.sh just runs all the mocha tests continuously. This file is only here so I don't have to remember the mocha command line...

#!/bin/sh

mocha --watch --require babel-core/register src/test/javascript/setup.js src/test/javascript/*.spec.js

setup.js is just some boiler plate we need to set up everything for testing react components:

var jsdom = require('jsdom').jsdom;

var exposedProperties = ['window', 'navigator', 'document'];

global.document = jsdom('');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
	if (typeof global[property] === 'undefined') {
		exposedProperties.push(property);
		global[property] = document.defaultView[property];
	}
});

global.navigator = {
	userAgent: 'node.js'
};

And environment.spec.js is the file that contains our first spec (or test):

import { expect } from 'chai';

describe('the environment', () => {
	it('should at least run this test, and it should be green', () => {
		expect(true).to.be.true;
	});
});

Running the tests now should result in exactly one green test.

Development Mode / Reload from Filesystem

So we already have a running application, but every time we change a javascript file, we have to re-run the browserify task and then re-start the application. This takes too much time. We want to be able to reload changed javascript files on the fly, without restarting the application.

Luckily, system.js is a great tool that does everything we need. But we only want to use it in development mode. In production, we still want to use our js/bundle.js that we create with browserify.

We only have to create different configurations for development and production and then create index.html dynamically so it either loads bundle.js or uses system.js.

web-frontend
\---src
    +---main
    |   +---java
    |   |   \---net.davidtanzer.reactspringboot.web
    |   |       \---MainPageController.java
    |   +---javascript
    |   \---resources
    |       +---static
    |        \---templates
    |           \---index.html
    \---test
        \---javascript

index.html is now a thymeleaf template that dynamically (generated on the server) selects how to load the javascript.

<html xmlns:th="http://www.thymeleaf.org">
	<head>
		<script th:if="${not bundleJavaScript}" src="https://jspm.io/system@0.19.js"></script>
	</head>
	<body>
		<div id="example">Example...</div>

		<!-- ******* Load bundled javascript (for production mode)             ******* -->
		<!-- ******* or javascript modules via system.js (in development mode) ******* -->
		<script th:if="${bundleJavaScript}" src="js/bundle.js"></script>
		<script th:if="${not bundleJavaScript}">
			System.config({
				transpiler: 'babel',
				babelOptions: {},
			});
			System.import('./main.js');
		</script>
		<!-- ************************************************************************* -->
	</body>
</html>

We need a controller class, MainPageController.java to actually set the value for the variable bundleJavaScript

package net.davidtanzer.reactspringboot.web;

import net.davidtanzer.jdefensive.Returns;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.HashMap;
import java.util.Map;

@Controller
public class MainPageController {
    @Value("${net.davidtanzer.webfrontend.bundledjs}")
    private boolean bundleJavaScript;

    @RequestMapping("/")
    public ModelAndView index() {
        Map<String, Object> model = new HashMap<>();

        model.put("bundleJavaScript", bundleJavaScript);

        return Returns.notNull(new ModelAndView("index", model));
    }
}

Now we have to add the configuration for development and production mode to the web application:

spring-boot-webapp
\---src
    \---main
        \---resources
            +---application.properties
            \---application-development.properties

application.properties contains the default values for production:

spring.profiles.active=production.properties

net.davidtanzer.webfrontend.bundledjs=true

And application-development.properties contains the overrides for development. It configures spring to load javascript files from the file system and to send a cache-control header with value "0" to the browser. Otherwise the browser would cache javascript files loaded by system.js and on-the-fly re-loading would not even work with a server restart.

spring.resources.staticLocations=file:web-frontend/src/main/javascript/,classpath:/static/
spring.resources.cache-period=0

net.davidtanzer.webfrontend.bundledjs=false

To activate the development profile, you have to add -Dspring.profiles.active=development to the Java command line:

Selecting development profile
Selecting development profile

Now everything works - You can start the server, load the application, change headerarea.js, reload in the browser, and you'll see the changed texts immediately.

What Next?

This basic setup works well for me right now, but there are still things left to do, for example:

  • Integrate redux
  • Integrate a REST backend
  • Run mocha test in the gradle build, and break the build when they fail.

Remember: If you just want to play around with what I did, don't repeat all my steps, clone my github project. This guide is just here to explain why the code is how it is right now, and how all the parts play together.

Do you have any questions? What else could I do? Is there anything I could do better? Please tell me!

Posting Type: 

My name is David Tanzer and I have been working as an independent software consultant since 2006. I help my clients to develop software right and to develop the right software by providing training, coaching and consultanting for teams and individuals.

Learn more...