Spring Boot, Vue.js, Axios and Thymeleaf with Bootstrap in 4 commits
Tags: beginners, vue, axios, spring
Published: 2020-02-04 05:12:42

Take a deep breath, hold...

In 4 short and easy commits you'll get a Spring Boot app serving server-side rendered pages via Thymeleaf (with Bootstrap) and Vue.js scripts making use of Axios to make asynchronous requests to the server to update pages with data without reloading the whole page from the server again.

... release! Explaining the previous phrase may take longer than to implement it. So let's do it.

# Commit #1 - Spring Initializr

This one is easy. The first commit is the base Spring Boot application from Spring Initializr + a few extras. 

You can generate your own version downloading [from this link](https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.2.4.RELEASE&packaging=jar&jvmVersion=1.8&groupId=com.example&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo&dependencies=web,data-jpa,thymeleaf,h2,lombok,devtools) (already populated with some dependencies).

Add the following to the `pom.xml` file after loading the project in your preferred IDE:

```xml
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>bootstrap</artifactId>
    <version>4.4.1</version>
</dependency>
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>webjars-locator</artifactId>
    <version>0.38</version>
</dependency>
```

# Commit #2 - The base app

This commit may be long but it's not complicated at all. It adds Role and User entities:

```java
@Entity(name = "role")
@Data
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String name;
}
```
```java
@Entity(name = "user")
@Data
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String firstName;
    private String lastName;

    @ManyToOne
    @JoinColumn(name = "role_id")
    private Role role;
}
```

And their corresponding Spring Data JPA repositories:

```java
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
}
```
```java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
```

Adds a controller to provide a server-side rendered page via Thymeleaf:

```java
@Controller
public class MainController {

    @RequestMapping("/")
    public String index() {
        return "index";
    }
}
```

And the `index.html` page itself on the `resources/templates` folder:

```html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <link href='/webjars/bootstrap/css/bootstrap.min.css' rel='stylesheet'>

    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>

<div th:replace="fragments/header :: header"></div>

<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
```

Did you notice the `<div th:replace...`? This is a neat feature of Thymeleaf to reuse code. Here it goes the reusable `header.html` on the `resources/templates/fragments` folder:

```html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <link href='/webjars/bootstrap/css/bootstrap.min.css' rel='stylesheet'>

    <meta charset="UTF-8">
    <title>Restaurants</title>
</head>
<body>

<nav class="navbar navbar-expand-lg navbar-dark bg-dark" th:fragment="header">
    <a class="navbar-brand" href="/">Home</a>
    <button aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"
            class="navbar-toggler" data-target="#navbarSupportedContent" data-toggle="collapse" type="button">
        <span class="navbar-toggler-icon"></span>
    </button>

    <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav mr-auto">
            <li class="nav-item dropdown">
                <a aria-expanded="false" aria-haspopup="true" class="nav-link dropdown-toggle" data-toggle="dropdown"
                   href="#" id="navbarDropdown" role="button">
                    Entities
                </a>
                <div aria-labelledby="navbarDropdown" class="dropdown-menu">
                    <a class="dropdown-item" href="/users">Users</a>
                    <a class="dropdown-item" href="/roles">Roles</a>
                    <div class="dropdown-divider"></div>
                    <div class="dropdown-item-text p-4 text-muted" style="max-width: 200px;">
                        <p>
                            Administrative pages to list, edit, create and remove entities.
                        </p>
                    </div>
                </div>
            </li>
        </ul>
        <form class="form-inline my-2 my-lg-0">
            <input aria-label="Search" class="form-control mr-sm-2" placeholder="Search" type="search">
            <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
        </form>
    </div>
</nav>

<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
```

# Commit #3 - The HTML templates for the entities

Adds server-side-rendered templates and controllers. Templates provide basic html pages for Roles and Users.

This is the new `RoleController`:

```java
@Controller
public class RoleController {

    @GetMapping("/roles")
    public String rolesPage() {
        return "roles";
    }
}
```

And the `UserController`:

```java
@Controller
public class UserController {

    @GetMapping("/users")
    public String users() {
        return "users";
    }
}
```

The HTML for the `roles.html` page:

```html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <link href='/webjars/bootstrap/css/bootstrap.min.css' rel='stylesheet'>

    <meta charset="UTF-8">
    <title>Roles</title>
</head>
<body>

<div th:replace="fragments/header :: header"></div>

<br><br>

<div class="container" id="main">

    <table class="table table-striped table-bordered">
        <thead>
        <tr>
            <th>Role ID</th>
            <th>Role Name</th>
            <th>Actions</th>
        </tr>
        </thead>
        <tbody>
        <tr>
            <td></td>
            <td></td>
            <td>
                <a>Edit</a>
                <a>Delete</a>
            </td>
        </tr>
        </tbody>
    </table>
</div>

<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
```

And `users.html` page:

```html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <link href='/webjars/bootstrap/css/bootstrap.min.css' rel='stylesheet'>

    <meta charset="UTF-8">
    <title>Users</title>
</head>
<body>

<div th:replace="fragments/header :: header"></div>

<br><br>

<div class="container" id="main">

    <table class="table table-striped table-bordered">
        <thead>
        <tr>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Role</th>
            <th>Actions</th>
        </tr>
        </thead>
        <tbody>
        <tr>
            <td></td>
            <td></td>
            <td></td>
            <td>
                <a>Edit</a>
                <a>Delete</a>
            </td>
        </tr>
        </tbody>
    </table>
</div>

<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
```

# Commit #4 - The Vue magic

Now we add Vue and Axios to the `pom.xml` file:

```xml
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>vue</artifactId>
    <version>2.6.11</version>
</dependency>
<dependency>
    <groupId>org.webjars.npm</groupId>
    <artifactId>axios</artifactId>
    <version>0.19.0</version>
</dependency>
```

Then a REST controller (this serializes the response to JSON by default) to return a list of Roles:

```java
@RestController
@RequestMapping("/api/v1")
public class RolesController {

    private final RoleRepository roleRepository;

    public RolesController(RoleRepository roleRepository) {
        this.roleRepository = roleRepository;
    }

    @GetMapping("roles")
    public List<Role> list() {
        return roleRepository.findAll();
    }
}
```

And another one for Users:

```java
@RestController
@RequestMapping("/api/v1")
public class UsersController {

    private final UserRepository userRepository;

    public UsersController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @GetMapping("/users")
    public List<User> list() {
        return userRepository.findAll();
    }
}
```

Finally, on the `roles.html` we edit the section where we want Vue.js to render the data and add a script:

```html
<!-- unrelated code omitted for brevity -->
<tr v-for="role in roles">
    <td>{{ role.id }}</td>
    <td>{{ role.name }}</td>
<!-- unrelated code omitted for brevity -->
```
```javascript
<!-- Vue.js imports -->
<script src="webjars/vue/vue.min.js"></script>
<script src="webjars/axios/dist/axios.min.js"></script>
<!-- Actual Vue.js script -->
<script>
    var app = new Vue({
        el: '#main',
        data() {
            return {
                roles: null
            }
        },
        mounted(){
            axios
                .get("/api/v1/roles")
                .then(response => (this.roles = response.data))
        },
    })
</script>
```

The same modifications are made on the `users.html`:

```html
<!-- unrelated code omitted for brevity -->
<tr v-for="user in users">
    <td>{{ user.firstName }}</td>
    <td>{{ user.lastName }}</td>
    <td>{{ user.role.name }}</td>
<!-- unrelated code omitted for brevity -->
```
```javascript
<!-- Vue.js imports -->
<script src="webjars/vue/vue.min.js"></script>
<script src="webjars/axios/dist/axios.min.js"></script>
<!-- Actual Vue.js script -->
<script>
    var app = new Vue({
        el: '#main',
        data() {
            return {
                users: null
            }
        },
        mounted(){
            axios
                .get("/api/v1/users")
                .then(response => (this.users = response.data))
        },
    })
</script>
```

# The app

You can see the app running [here](https://spring-thymeleaf-vue-crud.herokuapp.com/) (slightly modified since this is an ongoing analysis).

The repository and the aforementioned commits, also slightly modified, [here](https://github.com/brunodrugowick/spring-thymeleaf-vue-crud-example/commits/master).

{% github https://github.com/brunodrugowick/spring-thymeleaf-vue-crud-example no-readme %}


# AQAP Series

As Quickly As Possible (AQAP) is a series of quick posts on something I find interesting. I encourage (and take part on) the discussions on the comments to further explore the technology, library or code quickly explained here.

# Beginners tag

I'm using this tag for the second time. Since there are rules to use it, please let me know if there's something here you (or a beginner) don't understand or that I took for granted and you're (or a beginner may be :) confused about it.

---

Image by <a href="https://pixabay.com/pt/users/jaykingsta14-4885997/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=2358636">Jason King</a> por <a href="https://pixabay.com/pt/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=2358636">Pixabay</a>