In this post, we present how we can set up spring boot with FreeMarker and React. The process has three steps:
- Set up a standard spring boot application.
- Configure FreeMarker as the template engine and create a controller to return a html file based on the template.
- Install react and compile the javascript code.
The end result is a simple server that can return a html file based on a FreeMarker template and the html uses a javascript script compiled from React code.
Related Readings:
- Create React App without Create React App
- How to create a React app without using create-react-app
- CRUD Application With React and Spring Boot
Set up a spring application
It is straightforward to create a spring boot application. Go to https://start.spring.io/ and select the environment configuration. Don't forget to configure the dependencies. We will use FreeMarker as the template engine, so make sure the FreeMarker is added to the dependencies.

Click Generate button and a zip file will be generated. The content is a spring application that can run out of the box.
I also add other dependencies such as lombok and google guava. Here is the pom.xml file I use:
<?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>3.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</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-freemarker</artifactId>
<version>3.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.0.0</version> <!-- This is added by myself -->
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Checkpoint:
At this point, we should be able to run the spring application. FreeMarker library is already added to the dependencies but it's not used.
Set up FreeMarker
In this section, we will configure FreeMarker and create a controller which returns a html based on a template.
To configure FreeMarker, we need to create FreeMarkerViewResolver
and FreeMarkerConfigurer
beans. The configuration tells spring boot which files are templates and where to find them. The general practice is to use .ftl
as the suffix of a template file and templates are stored in resources/templates
in a Maven project.
package com.example.demo.springconfig;
@Configuration
public class FreemarkerConfiguration {
@Bean
public FreeMarkerViewResolver freemarkerViewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache(true);
// resolver.setPrefix("");
resolver.setSuffix(".ftl"); // This is the template of the template files.
return resolver;
}
@Bean
public FreeMarkerConfigurer freemarkerConfig() {
FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
// Instruct Freemarker to search for template in resources/templates
freeMarkerConfigurer.setTemplateLoaderPath("classpath:/templates/");
return freeMarkerConfigurer;
}
}
The second step is to create a template file. For demonstration purpose, we create a home_page.ftl
file in the resoures/templates
folder. We don't concern about the template syntax in this post. Any html
file would work.
The last step is to create a controller and specifies the request mapping.
@Controller
@Slf4j
public class HomePageController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String homepage(@ModelAttribute("model") ModelMap model) {
return "home_page";
}
}
Notice that the method returns the name of the template file without the suffix. If we go to http://localhost:8080/
, we should see the home page. (8080 is the default port).
Checkpoint:
In this section, we configured the FreeMarker. More specifically, we defined the template file suffix and the path of templates. A controller is also created, which returns a html page based on the home_page template.
If we go to http://localhost:8080/
, we should see the home page.
Set up React
There are two decisions to make
- Where to store the react code?
- Where to store compiled code? The compiled code is the one used in the template.
For example, we can create a js
directory in resources/static
folder as our root directory for React related code.
ThisProject/src/main/resources ├── application.properties ├── static │ └── js └── templates ├── home_page.ftl
From now on, we refer to resources/static/js
as the js root directory.
Now it's time to set up the React. First, make sure node and npm are installed. We can check this by running
node -v npm -v
Then, go the js root directory and execute the following commands:
npm i --save-dev webpack webpack-cli webpack-dev-server npm i --save-dev babel-loader @babel/preset-env @babel/core @babel/plugin-transform-runtime @babel/preset-react @babel/eslint-parser @babel/runtime @babel/cli npm i --save-dev eslint eslint-config-airbnb-base eslint-plugin-jest eslint-config-prettier path npm i react react-dom
Create a webpack.config.js
file in the js root directory. This file specifies
- the files we want to compile. These files are called entries. In our example, only one entry is configured but webpack also supports multiple entries.
- the output of the compiled files. In our example, we will generate an output file called
main.js
and it will be stored inpath.resolve(__dirname, "public")
. The__dirname
points to the js root directory.
const path = require("path"); /*We are basically telling webpack to take index.js from entry. Then check for all file extensions in resolve. After that apply all the rules in module.rules and produce the output and place it in main.js in the public folder.*/ module.exports={ /** "mode" * the environment - development, production, none. tells webpack * to use its built-in optimizations accordingly. default is production */ mode: "development", /** "entry" * the entry point */ entry: path.resolve(__dirname, "src", "index.js"), output: { /** "path" * the folder path of the output file */ path: path.resolve(__dirname, "public"), /** "filename" * the name of the output file */ filename: "main.js" }, /** "target" * setting "node" as target app (server side), and setting it as "web" is * for browser (client side). Default is "web" */ target: "web", resolve: { /** "extensions" * If multiple files share the same name but have different extensions, webpack will * resolve the one with the extension listed first in the array and skip the rest. * This is what enables users to leave off the extension when importing */ extensions: ['.js','.jsx','.json'], alias: { ScriptRoot: path.resolve(__dirname, 'src'), } }, module:{ /** "rules" * This says - "Hey webpack compiler, when you come across a path that resolves to a '.js or .jsx' * file inside of a require()/import statement, use the babel-loader to transform it before you * add it to the bundle. And in this process, kindly make sure to exclude node_modules folder from * being searched" */ rules: [ { test: /\.(js|jsx)$/, //kind of file extension this rule should look for and apply in test exclude: /node_modules/, //folder to be excluded use: 'babel-loader' //loader which we are going to use } ] } }
Create another file called .babelrc
in the js root direcroty.
{
/*
a preset is a set of plugins used to support particular language features.
The two presets Babel uses by default: es2015, react
*/
"presets": [
"@babel/preset-env", //compiling ES2015+ syntax
"@babel/preset-react" //for react
],
/*
Babel's code transformations are enabled by applying plugins (or presets) to your configuration file.
*/
"plugins": [
"@babel/plugin-transform-runtime"
]
}
The last step is to add a scripts
section to the package.json
file:
"scripts": { "build": "webpack --mode=development", "test": "test" },
To compile the code (assuming we already have a index.js
file created in resources/static/js/src
), we can run the command
npm run build
If everything works, a main.js
file should appear in resources/static/js/public
. This file can be used as an ordinary javascript script in the template/html files.
Checkpoint:
At this point, we should have the following project structure:
ThisProject/src/main/resources ├── application.properties ├── static │ └── js │ ├── node_modules │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── main.js │ ├── src │ │ └── index.js │ ├── .babelrc │ └── webpack.config.js └── templates └── home_page.ftl
----- END -----
©2019 - 2022 all rights reserved