Spring Boot CRUD Application with Thymeleaf

In this tutorial, we'll learn how to develop a CRUD web application with Spring Boot and Thymeleaf.

We will use an H2 database to store data.

1. Create Spring Boot Project

Spring Boot provides a web tool called Spring Initializer to bootstrap an application quickly. Just go to https://start.spring.io/ and generate a new spring boot project.

Use the below details in the Spring boot creation:

Project Name: springboot-thymeleaf-crud-tutorial

Project Type: Maven

Choose dependencies: Spring Web, Thymeleaf, Spring Data JPA, H2 Driver

Package name: com.sourcecodeexamples.springboot

2. Maven Dependencies

Open the pom.xml file and replace it with the following content:
<?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 http://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.1.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.sourcecodeexamples.springboot</groupId>
	<artifactId>springboot-thymeleaf-crud-tutorial</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springboot-thymeleaf-crud-tutorial</name>
	<description>springboot-thymeleaf-crud-tutorial</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		 <dependency>
                        <groupId>com.h2database</groupId>
                        <artifactId>h2</artifactId>
                        <scope>runtime</scope>
                </dependency> 
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

3. Model Layer

Let's create a new package called entity inside com.sourcecodeexamples.springboot and add a class named Employee.java with the following contents:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotBlank;

@Entity
public class Student {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    
    @NotBlank(message = "Name is mandatory")
    @Column(name = "name")
    private String name;
    
    @NotBlank(message = "Email is mandatory")
    @Column(name = "email")
    private String email;

    @Column(name = "phone_no")
    private long phoneNo;
    
    public Student() {}

    public Student(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public void setId(long id) {
        this.id = id;
    }
    
    public long getId() {
        return id;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void setEmail(String email) {
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    public long getPhoneNo() {
	return phoneNo;
    }

    public void setPhoneNo(long phoneNo) {
	this.phoneNo = phoneNo;
    }
}

4. Create Repository or DAO Layer

The next thing we’re gonna do is create a repository to access Employee’s data from the database.

The JpaRepository interface defines methods for all the CRUD operations on the entity, and a default implementation of the JpaRepository called SimpleJpaRepository.

Let’s create the repository now. First, create a new package called repository inside the base package com.sourcecodeexamples.springboot. Then, create an interface called EmployeeRepository and extend it from JpaRepository -

import java.util.List;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.sourcecodeexamples.springboot.entity.Student;
@Repository public interface StudentRepository extends CrudRepository<Student, Long> { List<Student> findByName(String name); }

5. Controller Layer

First, create a new package controller inside base package com.sourcecodeexamples.springboot. Then, create a new class EmployeeController.java with the following contents -

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.sourcecodeexamples.springboot.entity.Student;
import com.sourcecodeexamples.springboot.repository.StudentRepository;
@Controller @RequestMapping("/students/") public class StudentController { private final StudentRepository studentRepository; @Autowired public StudentController(StudentRepository studentRepository) { this.studentRepository = studentRepository; } @GetMapping("signup") public String showSignUpForm(Student student) { return "add-student"; } @GetMapping("list") public String showUpdateForm(Model model) { model.addAttribute("students", studentRepository.findAll()); return "index"; } @PostMapping("add") public String addStudent(@Valid Student student, BindingResult result, Model model) { if (result.hasErrors()) { return "add-student"; } studentRepository.save(student); return "redirect:list"; } @GetMapping("edit/{id}") public String showUpdateForm(@PathVariable("id") long id, Model model) { Student student = studentRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("Invalid student Id:" + id)); model.addAttribute("student", student); return "update-student"; } @PostMapping("update/{id}") public String updateStudent(@PathVariable("id") long id, @Valid Student student, BindingResult result, Model model) { if (result.hasErrors()) { student.setId(id); return "update-student"; } studentRepository.save(student); model.addAttribute("students", studentRepository.findAll()); return "index"; } @GetMapping("delete/{id}") public String deleteStudent(@PathVariable("id") long id, Model model) { Student student = studentRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("Invalid student Id:" + id)); studentRepository.delete(student); model.addAttribute("students", studentRepository.findAll()); return "index"; } }

6. View Layer

index.html

Let's create an index.html file under the resources/templates folder and add the following contents to it:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Users</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
	href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
	integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
	crossorigin="anonymous">
<link rel="stylesheet"
	href="https://use.fontawesome.com/releases/v5.4.1/css/all.css"
	integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz"
	crossorigin="anonymous">
<!-- <link rel="stylesheet" href="../css/shards.min.css"> -->
</head>
<body>
	<div class="container my-2">
		<div class="card">
			<div class="card-body">
				<div th:switch="${students}" class="container my-5">
					<p class="my-5">
						<a href="/students/signup" class="btn btn-primary"><i
							class="fas fa-user-plus ml-2"> Add Student</i></a>
					</p>
					<div class="col-md-10">
						<h2 th:case="null">No Students yet!</h2>
						<div th:case="*">
							<table class="table table-striped table-responsive-md">
								<thead>
									<tr>
										<th>Name</th>
										<th>Email</th>
										<th>Phone No</th>
										<th>Edit</th>
										<th>Delete</th>
									</tr>
								</thead>
								<tbody>
									<tr th:each="student : ${students}">
										<td th:text="${student.name}"></td>
										<td th:text="${student.email}"></td>
										<td th:text="${student.phoneNo}"></td>
										<td><a th:href="@{/students/edit/{id}(id=${student.id})}"
											class="btn btn-primary"><i class="fas fa-user-edit ml-2"></i></a></td>
										<td><a
											th:href="@{/students/delete/{id}(id=${student.id})}"
											class="btn btn-primary"><i class="fas fa-user-times ml-2"></i></a></td>
									</tr>
								</tbody>
							</table>
						</div>

					</div>
				</div>
			</div>
		</div>
	</div>
</body>
</html>

add-student.html

Let's create an add-student.html file under the resources/templates folder and add the following contents to it:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Add User</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
	href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
	integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
	crossorigin="anonymous">
<link rel="stylesheet"
	href="https://use.fontawesome.com/releases/v5.4.1/css/all.css"
	integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz"
	crossorigin="anonymous">
<!-- <link rel="stylesheet" href="../css/shards.min.css"> -->
</head>
<body>
	<div class="container my-5">
	<h3> Add Student</h3>
		<div class="card">
			<div class="card-body">
				<div class="col-md-10">
					<form action="#" th:action="@{/students/add}"
						th:object="${student}" method="post">
						<div class="row">
							<div class="form-group col-md-8">
								<label for="name" class="col-form-label">Name</label> <input
									type="text" th:field="*{name}" class="form-control" id="name"
									placeholder="Name"> <span
									th:if="${#fields.hasErrors('name')}" th:errors="*{name}"
									class="text-danger"></span>
							</div>
							<div class="form-group col-md-8">
								<label for="email" class="col-form-label">Email</label> <input
									type="text" th:field="*{email}" class="form-control" id="email"
									placeholder="Email"> <span
									th:if="${#fields.hasErrors('email')}" th:errors="*{email}"
									class="text-danger"></span>
							</div>

							<div class="form-group col-md-8">
								<label for="phoneNo" class="col-form-label">Phone No</label> <input
									type="text" th:field="*{phoneNo}" class="form-control"
									id="phoneNo" placeholder="PhoneNo"> <span
									th:if="${#fields.hasErrors('phoneNo')}" th:errors="*{phoneNo}"
									class="text-danger"></span>
							</div>

							<div class="col-md-6">
								<input type="submit" class="btn btn-primary" value="Add Student">
							</div>

							<div class="form-group col-md-8"></div>

						</div>
					</form>
				</div>
			</div>
		</div>
	</div>
</body>
</html>

update-student.html

Let's create an update-student.html file under the resources/templates folder and add the following contents to it:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Update User</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
	href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
	integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
	crossorigin="anonymous">
<link rel="stylesheet"
	href="https://use.fontawesome.com/releases/v5.4.1/css/all.css"
	integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz"
	crossorigin="anonymous">
</head>
<body>
	<div class="container my-5">
	<h3> Update Student</h3>
		<div class="card">
			<div class="card-body">
				<div class="col-md-8">
					<form action="#"
						th:action="@{/students/update/{id}(id=${student.id})}"
						th:object="${student}" method="post">
						<div class="row">
							<div class="form-group col-md-6">
								<label for="name" class="col-form-label">Name</label> <input
									type="text" th:field="*{name}" class="form-control" id="name"
									placeholder="Name"> <span
									th:if="${#fields.hasErrors('name')}" th:errors="*{name}"
									class="text-danger"></span>
							</div>
							<div class="form-group col-md-8">
								<label for="email" class="col-form-label">Email</label> <input
									type="text" th:field="*{email}" class="form-control" id="email"
									placeholder="Email"> <span
									th:if="${#fields.hasErrors('email')}" th:errors="*{email}"
									class="text-danger"></span>
							</div>
							<div class="form-group col-md-8">
								<label for="phoneNo" class="col-form-label">Phone No</label> <input
									type="text" th:field="*{phoneNo}" class="form-control"
									id="phoneNo" placeholder="phoneNo"> <span
									th:if="${#fields.hasErrors('phoneNo')}" th:errors="phoneNo"
									class="text-danger"></span>
							</div>

							<div class="form-group col-md-8">
								<input type="submit" class="btn btn-primary"
									value="Update Student">
							</div>
						</div>
					</form>
				</div>
			</div>
		</div>
	</div>
</body>
</html>

7. Run Spring Boot Application

We’ve successfully built all the APIs for our application. Let’s now run the app and test the APIs.

Just go to the root directory of the application and type the following command to run it -

$ mvn spring-boot:run

The application will start at Spring Boot’s default tomcat port 8080.

8. Demo

Let's access the above-deployed web application using http://localhost:8080 Initially, you don't have any students on the list so let's add a new student first.

Comments