Package Your React App with Spring Boot [A How-To Guide]

Author profile picture

@natural_eEmmanuella

Software developer in the DC

A little backstory on how this started. My team needed to save some on money on the infrastructure we were requesting and since most of the load for the application we wanted to build would be on the client-side rather than the service side. We decided to see if we could combine a Spring Application with a React app and serve up a single war file.

Find out how to combine Create React App with Spring Boot to give you a single war file 
The basic idea of what Spring Boot and Create React App do.
  1. Create React App helps you start a React project very quickly. It gives you all the basic things you need to get up and running asap.
  2. Spring boot helps you start and maintain a spring application quickly and easily.

Goals:

  1. Frontend and backend in a single war file with optimized productions build
  2. Keeping the benefits Create React App gives such as hot reloading

Setup:

  1. Must have java installed. Head over here to download a version
  2. Must have maven installed. For Mac, I used Homebrew (brew install maven )but otherwise, head here
  3. Must have node installed. For Mac, I used Homebrew (brew install node )but otherwise, head over here
* Side note: my IDE of choice is IntelliJ. When working on react code I usually switch over to VS Code. Feel Free to use what makes you feel comfortable
  1. Create an empty repo on Github and add a Read Me, gitignore, license, etc.
  2. Head over to https://start.spring.io to create your spring application and download locally. Group and Artifact can also be anything you want it to be.
  3. GroupId: e.the.awesome
    Artifact: spring-react-combo-app
3. Unzip the downloaded project into your git directory. Commit, Commit, Commit. Your SpringReactComboAppApplication should look like this.
package e.the.awesome.springreactcomboapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class SpringReactComboAppApplication  extends SpringBootServletInitializer{

	public static void main(String[] args) {
		SpringApplication.run(SpringReactComboAppApplication.class, args);
	}

}
4. Let’s create a basic service now. We’ll call it the DadJokesController. This should be created in the same folder as SpringReactComboAppApplication file. I’m aware this is not a proper Rest API format but let’s ignore that for now.
package e.the.awesome.springreactcomboapp;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DadJokesController {
    @GetMapping("/api/dadjokes")
    public String dadJokes() {
        return "Justice is a dish best served cold, if it were served warm it would be just water.";
    }
}
5. In your terminal run
mvn spring-boot:run
Then in your browser check http://localhost:8080/api/dadjokes . You should see the dad joke we added to our controller.
6. To create your React app, just run in the root directory
npx create-react-app basic-frontend-app
You can call it whatever you want, I just called mine basic-frontend-app
7. To run the front end application:
cd basic-frontend-app<br>npm start
After starting it should look like:
8. Since we want to integrate our Dad Jokes service into the frontend, first we will address proxy issues. If you’ve noticed, your service starts on localhost:8080 while the frontend starts on localhost:3000. If we try calling our service from the frontend, depending on your browser, you’ll get a CORS error.
The simplest way to address the issue is to have your front end proxy any requests from port 3000 to 8080. This change will be made in your package.json file
{
  "name": "basic-frontend-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.3.1",
    "react-dom": "^16.3.1",
    "react-scripts": "1.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "proxy": {
    "/api": {
      "target": "http://localhost:8080",
      "ws": true
    }
  }
}
Add the following to your frontend App.js file
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {

    state = {};

        componentDidMount() {
            this.dadJokes()
        }

    dadJokes = () => {
        fetch('/api/dadjokes')
            .then(response => response.text())
            .then(message => {
                this.setState({message: message});
            });
    };

    render() {
        return (
            <div className="App">
            <header className="App-header">
            <img src={logo} className="App-logo" alt="logo"/>
            <h3 className="App-title">{this.state.message}</h3>
            </header>
            <p className="App-intro">
            To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        </div>
    );
    }
}

export default App;
Restart the front end and you should be good. if you happen to get this error: I deleted my package-lock.json file and node_modules folder reinstalled npm packages and ran it again
9. Your application should now look like. You can see the results of the dad jokes API call.
10. Now that our basic front end and backend is complete, it’s time to create a production build and single war file.
Under the <dependencies> add this in
<!-- https://mvnrepository.com/artifact/com.github.eirslett/frontend-maven-plugin -->
<dependency>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.6</version>
</dependency>
Under the <plugins> section of the pom file, we will add the following commands which will do the following things when mvn clean install is run.
  1. npm install with the specified version of node
  2. run a production build of our frontend
  3. deposit the production build
  4. <plugin>
       <groupId>com.github.eirslett</groupId>
       <artifactId>frontend-maven-plugin</artifactId>
       <version>1.6</version>
       <configuration>
          <workingDirectory>basic-frontend-app</workingDirectory>
          <installDirectory>target</installDirectory>
       </configuration>
       <executions>
          <execution>
             <id>install node and npm</id>
             <goals>
                <goal>install-node-and-npm</goal>
             </goals>
             <configuration>
                <nodeVersion>v8.9.4</nodeVersion>
                <npmVersion>5.6.0</npmVersion>
             </configuration>
          </execution>
          <execution>
             <id>npm install</id>
             <goals>
                <goal>npm</goal>
             </goals>
             <configuration>
                <arguments>install</arguments>
             </configuration>
          </execution>
          <execution>
             <id>npm run build</id>
             <goals>
                <goal>npm</goal>
             </goals>
             <configuration>
                <arguments>run build</arguments>
             </configuration>
          </execution>
       </executions>
    </plugin>
    <plugin>
       <artifactId>maven-antrun-plugin</artifactId>
       <executions>
          <execution>
             <phase>generate-resources</phase>
             <configuration>
                <target>
                   <copy todir="${project.build.directory}/classes/public">
                      <fileset dir="${project.basedir}/basic-frontend-app/build"/>
                   </copy>
                </target>
             </configuration>
             <goals>
                <goal>run</goal>
             </goals>
          </execution>
       </executions>
    </plugin>
    Side note: order is important for your plugins so make sure your node/npm install execution is before copying the build file execution
11. After adding this, run mvn clean install and verify that the target/classes directory contains both frontend files and java files. And you should be good to go.
A final look at my pom file
So that’s all I got. If you would like to take a look at the repo or use it. It can be found here on my Github.
Next up is an article on how to deploy your war file on Heroku. Look forward to it!

Tags

The Noonification banner

Subscribe to get your daily round-up of top tech stories!