How to run Spring boot with FreeMarker and React

Subscribe Send me a message home page tags


#spring boot  #spring  #freemarker  #react  #webpack 

In this post, we present how we can set up spring boot with FreeMarker and React. The process has three steps:

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:

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.

spring_boot_setup.png

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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.

1
2
3
4
5
6
7
8
9
@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

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

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
    /*
        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 -----

Welcome to join reddit self-learning community.
Send me a message Subscribe to blog updates

Want some fun stuff?

/static/shopping_demo.png