Tutorial: “Spring Boot Thymeleaf Ajax Crud Example – SpringBoot RestAPIs CRUD Application with MySQL Examples – FullStack: Frontend (Bootstrap + Ajax JavaScript) to Backend (SpringBoot + MySQL)”
In the tutorial, I will introduce how to create a Fullstack SpringBoot RestAPIs CRUD Application to MySQL database using Spring WEB MVC framework and Spring JPA for building Backend and using Bootstrap, JQuery Ajax for building frontend client
Architecture Overview – Spring Boot Thymeleaf Ajax Crud Example

- For building RestAPIs in SpringBoot application, we use Spring MVC Web.
- For interacting with database MySQL/PostgreSQL, we use Spring JPA.
- We implement RestAPI’s URL in
RestAPIController.java
file to process bussiness logic. - For manipulating database’s records, we define a JPA model for mapping field data and use a JPA CRUD repository to do CRUD operation with MySQL/PostgreSQL.
Project Structure – Spring Boot Thymeleaf Ajax Crud Example

models
package definesCustomer
model andMessage
response class.repository
package defines Spring JPA repository classCustomerRepository
to do CRUD operation with database.service
package defines a middleware classCustomerServices
between Controller and Repository.controller
package defines a RestAPI ControllerRestAPIController
to handle POST/GET/PUT/DELETE request.src\main\resources\static
folder contains.html
page views.src\main\resources\static\js
folder contains.js
files that implements Ajax client to doPOST
/GET
/PUT
/DELETE
requests to SpringBoot server.
Project Goal – Spring Boot Thymeleaf Ajax Crud Example
– Add new Customer:


– List All Customers:

– Update a Customer

– Delete a Customer

– Check database:

Video Guide – Spring Boot Thymeleaf Ajax Crud Example
Create SpringBoot project – Spring Boot Thymeleaf Ajax Crud Example
For building CRUD Application, we need Spring Web, Spring JPA and MySQL or PostgreSQL driver, so we add below dependencies in pom.xml
file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
SpringBoot Database Configuration
We add database configuration in application.properties
:
spring.datasource.url=jdbc:mysql://localhost:3306/loizenjavadb
spring.datasource.username=root
spring.datasource.password=12345
spring.jpa.generate-ddl=true
#drop & create table again, good for testing, comment this in production
spring.jpa.hibernate.ddl-auto=create
SpringBoot Define Spring JPA Model – Spring Boot Thymeleaf Ajax Crud Example
We create a Customer
model class with 5 attributes: id
, firstname
, lastname
, address
, age
.

package com.loizenjava.crudapp.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* @Copyright by https://loizenjava.com
* @author https://loizenjava.com
* youtube loizenjava
*/
@Entity
@Table(name="customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column
private String firstname;
@Column
private String lastname;
@Column
private String address;
@Column
private int age;
public void setId(long id) {
this.id = id;
}
public long getId() {
return this.id;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getFirstname() {
return this.firstname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getLastname() {
return this.lastname;
}
public void setAddress(String address) {
this.address = address;
}
public String getAddress() {
return this.address;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
protected Customer() {}
public Customer(String firstname, String lastname, String address, int age) {
this.firstname = firstname;
this.lastname = lastname;
this.address = address;
this.age = age;
}
public String toString() {
return String.format("id=%d, firstname='%s', lastname'%s', address=%s, age=%d",
id, firstname, lastname, address, age);
}
}
– @Column
specifies the mapped column for a persistent property or field. If no Column annotation is specified, the default values apply.
– javax.persistence.Id
specifies the primary key of an entity.
– javax.persistence.Entity
specifies that the class is an entity. This annotation is applied to the entity class.
SpringBoot Implement JPA Repository and Service
For doing CRUD operations with database, we define an interface CustomerRepository
that extends class JpaRepository
:
@Repository
public interface CustomerRepository extends JpaRepository{
}
– We implement a middleware class service CustomerServices
between CustomerRepository
and RestAPIController
:
package com.loizenjava.crudapp.service;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.loizenjava.crudapp.model.Customer;
import com.loizenjava.crudapp.repository.CustomerRepository;
/**
* @Copyright by https://loizenjava.com
* @author https://loizenjava.com
* youtube loizenjava
*/
@Service
public class CustomerServices {
@Autowired CustomerRepository repository;
public Customer saveCustomer(Customer customer) {
return repository.save(customer);
}
public List<Customer> getCustomerInfos(){
return repository.findAll();
}
public Optional<Customer> getCustomerById(long id) {
return repository.findById(id);
}
public boolean checkExistedCustomer(long id) {
if(repository.existsById((long) id)) {
return true;
}
return false;
}
public Customer updateCustomer(Customer customer) {
return repository.save(customer);
}
public void deleteCustomerById(long id) {
repository.deleteById(id);
}
}
Implement SpringBoot RestAPIs Controller – POST/GET/PUT/DELETE
We implement a RestAPI Controller RestAPIController
to handle POST/GET/PUT/DELETE request-mapping with 5 APIs:
addNewCustomer(@RequestBody Customer customer)
handles POST request to add a new customer.ResponseEntity
handles GET request to retrieve all customers.retrieveCustomerInfo() getCustomerById(@PathVariable long id)
handles GET request to get a customer by given id.updateCustomerById(@RequestBody Customer _customer, @PathVariable long id)
handles PUT request to update a customerdeleteCustomerById(@PathVariable long id)
handles DELETE request to delete a Customer from database

@RestController
@RequestMapping("/api/customer")
public class RestAPIController {
@Autowired
CustomerServices customerServices;
...
}
SpringBoot Add New Customer Controller
@PostMapping("/create")
public ResponseEntity<Message> addNewCustomer(@RequestBody Customer customer) {
try {
Customer returnedCustomer = customerServices.saveCustomer(customer);
return new ResponseEntity<Message>(new Message("Upload Successfully!",
Arrays.asList(returnedCustomer), ""), HttpStatus.OK);
}catch(Exception e) {
return new ResponseEntity<Message>(new Message("Fail to post a new Customer!",
null, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
SpringBoot Get a Customer by ID Controller
@GetMapping("/findone/{id}")
public ResponseEntity<Message> getCustomerById(@PathVariable long id) {
try {
Optional<Customer> optCustomer = customerServices.getCustomerById(id);
if(optCustomer.isPresent()) {
return new ResponseEntity<Message>(new Message("Successfully! Retrieve a customer by id = " + id,
Arrays.asList(optCustomer.get()), ""), HttpStatus.OK);
} else {
return new ResponseEntity<Message>(new Message("Failure -> NOT Found a customer by id = " + id,
null, ""), HttpStatus.NOT_FOUND);
}
}catch(Exception e) {
return new ResponseEntity<Message>(new Message("Failure",
null, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
SpringBoot Get All Customers Controller
@GetMapping("/retrieveinfos")
public ResponseEntity<Message> retrieveCustomerInfo() {
try {
List<Customer> customerInfos = customerServices.getCustomerInfos();
return new ResponseEntity<Message>(new Message("Get Customers' Infos!",
customerInfos, ""), HttpStatus.OK);
}catch(Exception e) {
return new ResponseEntity<Message>(new Message("Fail!",
null, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
SpringBoot Delete a Customer Controller
@DeleteMapping("/deletebyid/{id}")
public ResponseEntity<Message> deleteCustomerById(@PathVariable long id) {
try {
// checking the existed of a Customer with id
if(customerServices.checkExistedCustomer(id)) {
customerServices.deleteCustomerById(id);
return new ResponseEntity<Message> (new Message("Successfully! Delete a Customer with id = " + id,
null, ""), HttpStatus.OK);
}else {
return new ResponseEntity<Message>(new Message("Failer! Can NOT Found a Customer "
+ "with id = " + id, null, ""), HttpStatus.NOT_FOUND);
}
}catch(Exception e) {
return new ResponseEntity<Message>(new Message("Failure",
null, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
SpringBoot Update a Customer Controller
@PutMapping("/updatebyid/{id}")
public ResponseEntity<Message> updateCustomerById(@RequestBody Customer _customer,
@PathVariable long id) {
try {
if(customerServices.checkExistedCustomer(id)) {
Customer customer = customerServices.getCustomerById(id).get();
//set new values for customer
customer.setFirstname(_customer.getFirstname());
customer.setLastname(_customer.getLastname());
customer.setAddress(customer.getAddress());
customer.setAge(_customer.getAge());
// save the change to database
customerServices.updateCustomer(customer);
return new ResponseEntity<Message>(new Message("Successfully! Updated a Customer "
+ "with id = " + id,
Arrays.asList(customer), ""), HttpStatus.OK);
}else {
return new ResponseEntity<Message>(new Message("Failer! Can NOT Found a Customer "
+ "with id = " + id,
null, ""), HttpStatus.NOT_FOUND);
}
}catch(Exception e) {
return new ResponseEntity<Message>(new Message("Failure",
null, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Backend Testing – Spring Boot Thymeleaf Ajax Crud Example
1. Testcase 1 – SpringBoot Post RestAPI – Add new Customer:
– Post a Customer:

– Check database records:

2. Testcase 2 – SpringBoot RestAPI Get a Customer by Id:
– Get a Customer with id = 4:

3. Testcase 3 – SpringBoot Get RestAPI – Retrieve All Customers:

4. Testcase 4 – Delete a Customer by Id:
– Delete a Customer with id = 2:

5. Testcase 5 – SpringBoot Put RestAPI – Update a Customer:

Create SpringBoot Thymeleaf Index.html
We create a index.html
page for adding a new customer:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Build CRUD Nodejs Application</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/js/post_customer.js"></script>
</head>
<body>
<div class="container">
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" href="/">Add New</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/customers.html">All Customers</a>
</li>
</ul>
<div class="row">
<div class="col-sm-7" style="background-color:#e6fffa; padding:10px; border-radius:3px">
<h3>Add new Customer</h3>
<form id="add_new_customer">
<div class="form-group">
<label class="control-label" for="firstname">First Name:</label>
<input type="text" class="form-control" id="firstname"
placeholder="Enter First Name" name="firstname" required></input>
</div>
<div class="form-group">
<label class="control-label" for="lastname">Last Name:</label>
<input type="text" class="form-control" id="lastname"
placeholder="Enter Last Name" name="lastname" required></input>
</div>
<div class="form-group">
<label class="control-label" for="address">Address:</label>
<input type="text" class="form-control" id="address"
placeholder="Enter Address" name="address" required></input>
</div>
<div class="form-group">
<label class="control-label" for="age">Age:</label>
<input type="number" class="form-control" id="age"
placeholder="Enter Age" name="age" min="18" max="60" required></input>
</div>
<button type="submit" class="btn btn-danger" id="btn-add-new-customer">Update</button>
</form>
<div id="response" style="display:none; margin-top:10px">
</div>
</div>
</div>
<hr>
</div>
</body>
</html>
Thymeleaf show Customers and Update Page
Coding:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Build CRUD Nodejs Application</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/js/update_customer.js"></script>
<script type="text/javascript" src="/js/delete_customer.js"></script>
<script type="text/javascript" src="/js/get_customer.js"></script>
</head>
<body>
<div class="container">
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" href="/">Add New</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/customers/">All Customers</a>
</li>
</ul>
<div id="div_customer_table">
<h1>Customers Table</h1>
<div class="row col-md-6 table-responsive">
<table id="customerTable" class="table table-striped">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Address</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-sm-6" style="display: none;
background-color:#e6fffa; padding:10px; border-radius:3px"
id="div_customer_updating">
<form id="update_customer_form">
<div class="form-group">
<label for="customer_id">Id:</label>
<input type="text" class="form-control" id="customer_id" readonly>
</div>
<div class="form-group">
<label for="customer_first_name">First Name:</label>
<input type="text" class="form-control" placeholder="Enter First Name" id="customer_first_name">
</div>
<div class="form-group">
<label for="customer_last_name">Last Name:</label>
<input type="text" class="form-control" placeholder="Enter Last Name" id="customer_last_name">
</div>
<div class="form-group">
<label for="customer_address">Address:</label>
<input type="text" class="form-control" placeholder="Enter Address" id="customer_address">
</div>
<div class="form-group">
<label for="customer_age">Age:</label>
<input type="number" class="form-control"
placeholder="Enter Age" id="customer_age" min="18" max="60">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<div id="response" style="display:none; margin:10px">
</div>
</div>
</div>
<hr>
<!-- The Modal -->
<div class="modal fade" id="delete-modal">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">Delete!</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<!-- Modal body -->
<div class="modal-body">
</div>
<!-- Modal footer -->
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="model-delete-btn">Delete</button>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
Thymeleaf Ajax POST a Customer
– post_customer.js
:
$(document).ready(function() {
$("#add_new_customer").submit(function(evt) {
evt.preventDefault();
// PREPARE FORM DATA
let formData = {
firstname : $("#firstname").val(),
lastname : $("#lastname").val(),
address: $("#address").val(),
age: $("#age").val()
}
$.ajax({
url: '/api/customer/create',
type: 'POST',
contentType : "application/json",
data: JSON.stringify(formData),
dataType : 'json',
async: false,
cache: false,
success: function (response) {
let customer = response.customers[0];
let customerString = "{ name: " + customer.firstname + " " + customer.lastname +
", address: " + customer.address +
", age: " + customer.age + " }"
let successAlert = '<div class="alert alert-success alert-dismissible">' +
'<button type="button" class="close" data-dismiss="alert">×</button>' +
'<strong>' + response.message + '</strong> Customer\'s Info = ' + customerString;
'</div>'
$("#response").append(successAlert);
$("#response").css({"display": "block"});
resetUploadForm();
},
error: function (response) {
let errorAlert = '<div class="alert alert-danger alert-dismissible">' +
'<button type="button" class="close" data-dismiss="alert">×</button>' +
'<strong>' + response.message + '</strong>' + ' ,Error: ' + message.error +
'</div>'
$("#response").append(errorAlert);
$("#response").css({"display": "block"});
resetUploadForm();
}
});
});
function resetUploadForm(){
$("#firstname").val("");
$("#lastname").val("");
$("#address").val("");
$("#age").val("");
}
(function(){
let pathname = window.location.pathname;
if(pathname === "/"){
$(".nav .nav-item a:first").addClass("active");
} else if (pathname == "/customers.html") {
$(".nav .nav-item a:last").addClass("active");
}
})();
});
Thymeleaf Ajax GET all Customers
– get_customer.js
:
$(document).ready(function(){
(function(){
$.ajax({
type : "GET",
url : "/api/customer/retrieveinfos",
success: function(response){
$.each(response.customers, (i, customer) => {
/* <button type="button" class="btn btn-danger btn_delete" data-toggle="modal" data-target="#myModal">
Open modal
</button>*/
let deleteButton = '<button ' +
'id=' +
'\"' + 'btn_delete_' + customer.id + '\"'+
' type="button" class="btn btn-danger btn_delete" data-toggle="modal" data-target="#delete-modal"' +
'>×</button>';
let get_More_Info_Btn = '<button' +
' id=' + '\"' + 'btn_id_' + customer.id + '\"' +
' type="button" class="btn btn-info btn_id">' +
customer.id +
'</button>';
let tr_id = 'tr_' + customer.id;
let customerRow = '<tr id=\"' + tr_id + "\"" + '>' +
'<td>' + get_More_Info_Btn + '</td>' +
'<td class=\"td_first_name\">' + customer.firstname.toUpperCase() + '</td>' +
'<td class=\"td_address\">' + customer.address + '</td>' +
'<td>' + deleteButton + '</td>' +
'</tr>';
$('#customerTable tbody').append(customerRow);
});
},
error : function(e) {
alert("ERROR: ", e);
console.log("ERROR: ", e);
}
});
})();
(function(){
let pathname = window.location.pathname;
if (pathname == "/customers.html") {
$(".nav .nav-item a:last").addClass("active");
}
})();
});
Thymeleaf Ajax DELETE data
– delete_customer.js
file:
$(document).ready(function(){
let customerId = 0;
$(document).on("click", "#div_customer_table table button.btn_delete", function() {
let btn_id = (event.srcElement.id);
customerId = btn_id.split("_")[2];
$("div.modal-body")
.text("Do you want delete a Customer with id = " + customerId + " ?");
$("#model-delete-btn").css({"display": "inline"});
});
$(document).on("click", "#model-delete-btn", function() {
$.ajax({
url: '/api/customer/deletebyid/' + customerId,
type: 'DELETE',
success: function(response) {
$("div.modal-body")
.text("Delete successfully a Customer with id = " + customerId + "!");
$("#model-delete-btn").css({"display": "none"});
$("button.btn.btn-secondary").text("Close");
// delete the customer row on html page
let row_id = "tr_" + customerId;
$("#" + row_id).remove();
$("#div_customer_updating").css({"display": "none"});
},
error: function(error){
console.log(error);
$("#div_customer_updating").css({"display": "none"});
alert("Error -> " + error);
}
});
});
});
Thymeleaf Ajax Put data
– update_customer.js
:
$(document).ready(function(){
$("#update_customer_form").submit(function(evt) {
evt.preventDefault();
try {
let customerId = $("#customer_id").val();
// PREPARE FORM DATA
let formData = {
firstname : $("#customer_first_name").val(),
lastname : $("#customer_last_name").val(),
address: $("#customer_address").val(),
age: $("#customer_age").val()
}
$.ajax({
url: '/api/customer/updatebyid/' + customerId + "/",
type: 'PUT',
contentType : "application/json",
data: JSON.stringify(formData),
dataType : 'json',
async: false,
cache: false,
success: function (response) {
let customer = response.customers[0];
let customerString = "{firstname:" + customer.firstname +
" ,lastname:" + customer.lastname +
", address:" + customer.address +
", age:" + customer.age + "}"
let successAlert = '<div class="alert alert-success alert-dismissible">' +
'<button type="button" class="close" data-dismiss="alert">×</button>' +
'<strong>' + response.message + '</strong> Customer\'s Info = ' + customerString;
'</div>'
// change the updated data for customer table record
$("#tr_" + customerId + " td.td_first_name").text(customer.firstname.toUpperCase());
$("#tr_" + customerId + " td.td_address").text(customer.address.toUpperCase());
$("#response").empty();
$("#response").append(successAlert);
$("#response").css({"display": "block"});
},
error: function (response) {
let errorAlert = '<div class="alert alert-danger alert-dismissible">' +
'<button type="button" class="close" data-dismiss="alert">×</button>' +
'<strong>' + response.message + '</strong>' + ' ,Error: ' + message.error +
'</div>';
$("#response").empty();
$("#response").append(errorAlert);
$("#response").css({"display": "block"});
}
});
} catch(error){
console.log(error);
alert(error);
}
});
$(document).on("click", "table button.btn_id", function(){
let id_of_button = (event.srcElement.id);
let customerId = id_of_button.split("_")[2];
$.ajax({
url: '/api/customer/findone/' + customerId,
type: 'GET',
success: function(response) {
let customer = response.customers[0];
$("#customer_id").val(customer.id);
$("#customer_first_name").val(customer.firstname);
$("#customer_last_name").val(customer.lastname);
$("#customer_address").val(customer.address);
$("#customer_age").val(customer.age);
$("#div_customer_updating").css({"display": "block"});
},
error: function(error){
console.log(error);
alert("Error -> " + error);
}
});
});
});
Frontend Testing – Spring Boot Thymeleaf Ajax Crud Example
1. Testcase 1 – Add new Customer:


2. Testcase 2 – Show All Customer:


3. Testcase 3 – Get a Customer:


4. Testcase 4 – Update a Customer:


5. Testcase 5 – Delete a Customer:



Read More
Sourcecode – Spring Boot Thymeleaf Ajax Crud Example
I include a running sourcecode for the tutorial with the implemented features:
- SpringBoot Backend Development
- Bootstrap and Ajax Frontend Development
– Github Sourcecode: