DEV Community

Arpan Banerjee
Arpan Banerjee

Posted on • Edited on

Run React Frontend and SpringBoot Backend on the same port and Package them as a single artifact !!

Find out how to combine create-react-app with Spring Boot to give you a single artifact.

Hey there! I am a full stack developer with 3+ years of experience based out of West Bengal, India. Today I will walk you through a setup trick to prepare your project structure for running react and spring boot app on the same port, and package them as a single artifact. You will see how easy this can make development when dealing with both of these game-changing technologies.

Here is the GitHub link of the demo that I am going to explain here.

Advantages of this setup

  1. Combines the ease of development of both create-react-app and Spring Boot.

  2. You can use the hot reload feature of create-react-app while developing the frontend, save the page after making changes and it will reflect immediately on the browser.

  3. Using Spring Boot you can easily manage your development versions (using SNAPSHOTs) and release versions in production. You can detect build version and time at runtime in Spring Boot using Spring Actuator or Spring Admin, which can provide the build metadata functionality plus a lot more.

  4. Package Frontend and Backend in a single war/jar file with optimized production build.

I learned this in one of my recent projects. It has several spring boot microservices with react frontend packaged and deployed as single artifacts of their own.

So, here is a quick guide how to run a react frontend and a spring boot backend on the same port and how to package them as a single jar file.

First, create a spring boot project with https://start.spring.io. Add the Web dependency. Set the groupId and artifactId to whatever you want. Generate the project and unzip it into your project directory.

Or, if you are using Spring Tools Suite you can simply click
File->New->Spring Starter Project and mention the required details to create a spring boot project.

Your initial pom.xml should look like this.

image

The project structure should look like this.

image

You can run the app now and see if it runs without any errors.
Though you won't see anything if you hit http://localhost:8080/.

image

Now, go to src/main/resources, create a static folder. Inside it create a test.html file with some text in it.

image

Now, if you restart the application and hit http://localhost:8080/test.html you will see it spins up your html page in port 8080. You can see the contents of your file in the browser.

image

Serving the React App from Spring Boot on port 8080 and Packaging them together

We will exploit the above mentioned feature of Spring Boot to serve the single page of the react project. We will serve a html page from the static folder in the target directory, not in the source directory.

Now let's create a react app, using create-react-app. Navigate to your folder till src/main in a terminal
D:\Codes_projects\springboot_react_maven_plugin\Spring_React_maven-plugin\src\main> and run npx create-react-app frontend.

This should create a react app inside src/main. Now if you look at your folder structure it should look like this.

image

You can run the react app, by doing cd frontend and then yarn start or npm start. It should spin up the react app on http://localhost:3000.

image

You can create a production build of the frontend by running yarn build. Your folder structure after running the build command will look like this.

image

You need to copy this production build place it inside the target/classes directory of your project to enable Spring Boot to serve this index.html when you start your project

So, there are two steps-

  1. create a production build of the frontend
  2. copy the production build into ${target/classes/}

Of course you are not going to do that manually.
We we will use two maven plugins for that.

  1. frontend-maven-plugin for step 1.
  2. maven-resources-plugin for step 2.

Add the following to your pom.xml under the plugins section and update the properties section as shown.



   <properties>
        <java.version>1.8</java.version>
        <frontend-src-dir>${project.basedir}/src/main/frontend</frontend-src-dir>
        <node.version>v14.15.4</node.version>
        <yarn.version>v1.16.0</yarn.version>
        <frontend-maven-plugin.version>1.7.6</frontend-maven-plugin.version>
    </properties>


Enter fullscreen mode Exit fullscreen mode


           <plugin>
                <groupId>com.github.eirslett</groupId>
                <artifactId>frontend-maven-plugin</artifactId>
                <version>${frontend-maven-plugin.version}</version>

                <configuration>
                    <nodeVersion>${node.version}</nodeVersion>
                    <yarnVersion>${yarn.version}</yarnVersion>
                    <workingDirectory>${frontend-src-dir}</workingDirectory>
                    <installDirectory>${project.build.directory}</installDirectory>
                </configuration>

                <executions>
                    <execution>
                        <id>install-frontend-tools</id>
                        <goals>
                            <goal>install-node-and-yarn</goal>
                        </goals>
                    </execution>

                    <execution>
                        <id>yarn-install</id>
                        <goals>
                            <goal>yarn</goal>
                        </goals>
                        <configuration>
                            <arguments>install</arguments>
                        </configuration>
                    </execution>

                    <execution>
                        <id>build-frontend</id>
                        <goals>
                            <goal>yarn</goal>
                        </goals>
                        <phase>prepare-package</phase>
                        <configuration>
                            <arguments>build</arguments>
                        </configuration>
                    </execution>
                </executions>
            </plugin>   


Enter fullscreen mode Exit fullscreen mode

Now if you run mvn clean install from
D:\Codes_projects\springboot_react_maven_plugin\Spring_React_maven-plugin>
maven will install npm, yarn and node locally and run npm build in the frontend directory.

image

The folder structure would look like this-

image

Now, add the second plugin to your pom.xml.



          <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <executions>
                    <execution>
                        <id>position-react-build</id>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <phase>prepare-package</phase>
                        <configuration>
                            <outputDirectory>${project.build.outputDirectory}/static</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>${frontend-src-dir}/build</directory>
                                    <filtering>false</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>


Enter fullscreen mode Exit fullscreen mode

Add the following dependency in the dependencies section.



       <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>


Enter fullscreen mode Exit fullscreen mode

Modify the spring-boot-maven-plugin to include a configuration section.



          <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>


Enter fullscreen mode Exit fullscreen mode

This will ensure that the frontend build files are copied after they have been generated by yarn build.
Now, run mvn clean install again and inspect the target/classes/static directory from your file manager.

image

It will have the frontend production build files in there.

Your final pom.xml will look like this.



<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springreact</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Run React Frontend and SpringBoot Backend on the same port.</description>
    <properties>
        <java.version>1.8</java.version>
        <frontend-src-dir>${project.basedir}/src/main/frontend</frontend-src-dir>
        <node.version>v14.15.4</node.version>
        <yarn.version>v1.16.0</yarn.version>
        <frontend-maven-plugin.version>1.7.6</frontend-maven-plugin.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.github.eirslett</groupId>
                <artifactId>frontend-maven-plugin</artifactId>
                <version>${frontend-maven-plugin.version}</version>

                <configuration>
                    <nodeVersion>${node.version}</nodeVersion>
                    <yarnVersion>${yarn.version}</yarnVersion>
                    <workingDirectory>${frontend-src-dir}</workingDirectory>
                    <installDirectory>${project.build.directory}</installDirectory>
                </configuration>

                <executions>
                    <execution>
                        <id>install-frontend-tools</id>
                        <goals>
                            <goal>install-node-and-yarn</goal>
                        </goals>
                    </execution>

                    <execution>
                        <id>yarn-install</id>
                        <goals>
                            <goal>yarn</goal>
                        </goals>
                        <configuration>
                            <arguments>install</arguments>
                        </configuration>
                    </execution>

                    <execution>
                        <id>build-frontend</id>
                        <goals>
                            <goal>yarn</goal>
                        </goals>
                        <phase>prepare-package</phase>
                        <configuration>
                            <arguments>build</arguments>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <executions>
                    <execution>
                        <id>position-react-build</id>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <phase>prepare-package</phase>
                        <configuration>
                            <outputDirectory>${project.build.outputDirectory}/static</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>${frontend-src-dir}/build</directory>
                                    <filtering>false</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>


Enter fullscreen mode Exit fullscreen mode

Now you are ready to go! Run the project and go to http://localhost:8080/index.html VOILA!! You have run your React app via Spring Boot.

image

Taking a step further

You can add rest controllers and expose an endpoint to be the starting point of the application. Like an Index Controller , whenever the application loads, spring boot will spin up the index.html file having the static contents from the optimized build of the frontend project from the target directory. So that you can simply visit http://localhost:8080 and not hardcode the index.html file name.

Let's do that.
Add a controller, in the following folder structure.

image



package com.springreact.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class IndexController {

    @GetMapping("")
    public ModelAndView home() {
        ModelAndView mav=new ModelAndView("index");
        return mav;
    }

}


Enter fullscreen mode Exit fullscreen mode

Run your application and hit http://localhost:8080/.
Did you see anything? No, right!

You have to add another dependency for this to work.



        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>


Enter fullscreen mode Exit fullscreen mode

Now, by default thymleaf looks for our templates in src/main/resources/templates. We can put our templates there and organize them in sub-directories and have no issues.

So, let's try that. Add a html file inside src/main/resources/templates named index.html and put some content in it.

Run the application and visit http://localhost:8080. You will see the index.html from src/main/resources/templates being served in port 8080.

image

Now, to change this default behaviour and make thymleaf serve static content from another location, you need to add an entry to the application.properties file.



spring.thymeleaf.prefix=file:///D:///Codes_projects//springboot_react_maven_plugin//Spring_React_maven-plugin//target//classes//static/


Enter fullscreen mode Exit fullscreen mode

Here, I have hardcoded the path.

Finally, now if you update your project and start it. You can see the react app being served directly on port 8080.

image

Conclusion

You have learned how to use spring boot to run a react app. You can run both your frontend and backend on the same port. I have explained what you need to do to get the setup done. You can now create a meaningful application, and see how easy and quick it is to develop with this setup. You can now run the frontend application using React-scripts by running cd frontend; yarn start, and develop using the hot reload feature of create-react-app and have meaningful error messages, while being able to deploy the application to test and production environments as a single artifact.

Thanks for reading!

Please leave a comment if you find this helpful. Any feedback or suggestions are welcome.

Top comments (10)

Collapse
 
mostafamirzaee profile image
Mostafa S.Mirzaei

Bro, you guys are saints. you who spend a lot of time to share your knowledge freely by righting these articles. I will join you guys soon. thanks a lot for making other developers' lives much easier.

Collapse
 
peterkronenberg profile image
Peter Kronenberg

Thank you so much for this! Have been trying to figure this out for days. I'm an experienced Java developer, but new to Javascript frameworks. It was so hard to find a clear article on how to do this.

Collapse
 
peterkronenberg profile image
Peter Kronenberg • Edited

Hope you don't mind some questions. If there is a better place to post them, let me know. I got your demo working. But when I try it with my React app, I can't get anything other than index.html to show. If I just enter localhost:8080, I get my default React page. If I enter localhost:8080/index.html, I get the generic index.html with no dynamic content. (tried to attached screenshot, but it doesn't work)
If I enter any of my other routes, such as localhost:8080/login, I get the Whitelabel error page. Any suggestions? Do you have a sample that has anything other than the default React app?

Note that if I browse directly to React at localhost:3000, the client responds properly

Collapse
 
shikharajpoot13 profile image
SHIKHA RAJPOOT

Hey Peter, did you find the anwser to your question? If yes please do share the solution.

Collapse
 
hendisantika profile image
Hendi Santika

Can you update it using the latest version?
It error on my side while running mvn clean package

Thanks

Collapse
 
ivanbo97 profile image
ivanbo97 • Edited

Hello and thank you for the in-depth tutorial !

In case someone is interested. There is also an alternative way of combining create-react-app with Spring Boot in a single artifact which does not require adding of maven-resources-plugin. In configuration part of frontend-maven-plugin you just need to set an environment variable with the name BUILD_PATH (ref : create-react-app.dev/docs/advanced...) and value ${basedir}/target/classes/static . In this way you are forcing frontend-maven-plugin to output the generated production build directly in target/classes/static. Therefore, we do not need maven-resources-plugin for making intermediate copies. I hope it makes sense.

Collapse
 
borche profile image
Andreas Börjesson

Good article. Thanks for sharing.

Collapse
 
declantreanor profile image
declanTreanor

A more comprehensive guide than this probably couldn't be found. Armed with these two great tools in unison, I'm unstoppable, or at least, I should be.

Collapse
 
azizbekavazov profile image
AzizbekAvazov • Edited

Thanks for this tutorial!
Here spring.thymeleaf.prefix path is hardcoded. How can we give it relative path?

Collapse
 
markhennings profile image
Mark Hennings

This path worked for me in my application.properties:
spring.thymeleaf.prefix=file:./target/classes/static/