Develop Spring Boot Application with PostgreSQL and Thymeleaf

Published
In the previous article, Set Up Spring Boot Application using Spring Initializr, we covered the setup of a Spring Boot application. Now, we will configure the Spring Boot application to integrate with a PostgreSQL database and implement event creation functionality using Spring MVC architecture.
By the end, we will develop an Event Registration System that allows users to register for an event. Also, Admin to create the event and view the registered users. However, In this article, we will focus on the event creation functionality.

Database Schema for Event Registration System

Before we get our hands dirty with development, let's take a look at the database diagram of the Event Registration System. It is a best practice to have a clear understanding of the database schema before starting the development.Event Registration Database Diagram
Add the following SQL code in resources / schema.sql to create the database tables for the Event Registration System. Spring Boot will automatically execute this script on application startup to create the necessary tables.
resources / schema.sql
CREATE TABLE IF NOT EXISTS user_roles ( id SERIAL PRIMARY KEY, user_role_name VARCHAR(100) NOT NULL ); CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100), email_address VARCHAR(100) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, profile_image VARCHAR(100), user_role_id INT NOT NULL REFERENCES user_roles(id) ); CREATE TABLE IF NOT EXISTS events ( id SERIAL PRIMARY KEY, event_name VARCHAR(100) NOT NULL, event_description VARCHAR(500), event_location VARCHAR(100) NOT NULL, event_start_datetime TIMESTAMP NOT NULL, event_end_datetime TIMESTAMP NOT NULL, registration_start_datetime TIMESTAMP NOT NULL, registration_end_datetime TIMESTAMP NOT NULL ); CREATE TABLE IF NOT EXISTS event_participants ( id SERIAL PRIMARY KEY, user_id INT NOT NULL REFERENCES users(id), event_id INT NOT NULL REFERENCES events(id), UNIQUE (user_id, event_id) );

Provision PostgreSQL Database and Configure PostgreSQL Driver

To provision PostgreSQL database, we will use Docker to run a PostgreSQL container. Here is a sample docker-compose.yml file to run PostgreSQL container. Make sure to have Docker installed on your machine.
docker-compose.yml
services: postgres: image: postgres:14.7 container_name: postgres ports: - "5432:5432" environment: POSTGRES_DB: "event_registration" POSTGRES_USER: "postgres" POSTGRES_PASSWORD: "postgres"
Run the following command to start the PostgreSQL container:
docker-compose up -d
Now, To configure the PostgreSQL driver in the Spring Boot application, update the application.yml file with the following config:
resources / application.yml
server: port: 8080 spring: application: name: event-registration datasource: url: jdbc:postgresql://localhost:5432/event_registration username: postgres password: postgres jpa: show-sql: true sql: init: mode: always
Make sure sql init mode is set to never to avoid executing the schema.sql file in the production environment.
Now, we can start the Spring Boot application and it will automatically create the necessary tables in the PostgreSQL database. You can verify the tables by connecting to the PostgreSQL database using a database client like DBeaver or pgAdmin.

Event Creation with Spring MVC Architecture

In a typical real-world application, event creation functionality is restricted to admin users. However, to keep things simple, we'll allow all users to create events in this implementation. This approach will allow to focus on the technical aspects without getting into complex role-based access control at this stage.
We'll begin by implementing the backend functionality. This includes creating Event entity, defining Repository interface for database operations, building service layer to handle business logic, and setting up controller to handle form submissions and page rendering.
entities / Event.java
@Entity @Table(name = "events") @Getter @Setter public class Event { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String eventName; private String eventDescription; private String eventLocation; private LocalDateTime eventStartDatetime; private LocalDateTime eventEndDatetime; private LocalDateTime registrationStartDatetime; private LocalDateTime registrationEndDatetime; }
repositories / EventRepository.java
@Repository public interface EventRepository extends JpaRepository<Event, Integer> { }
services / EventService.java
@Service @AllArgsConstructor public class EventService { private final EventRepository eventRepository; public Event createEvent(Event event) { return eventRepository.save(event); } }
Instead of exposing REST APIs, we're working with traditional Spring MVC patterns to support server-side rendering with Thymeleaf. Once the backend is in place, we can test the form-based submission using tools like Postman to ensure that event data is processed and stored in database correctly.
controllers / EventController.java
@Controller @RequestMapping("/event") @AllArgsConstructor public class EventController { private final EventService eventService; @GetMapping("/create") public String showCreateEventForm(Model model) { model.addAttribute("event", new Event()); return "event/create"; } @PostMapping("/create") public String createEvent(@ModelAttribute Event event) { eventService.createEvent(event); return "redirect:/event/list"; } @GetMapping("/list") public String listEvents(Model model) { model.addAttribute("events", eventService.getAllEvents()); return "event/list"; } }
Create Event Request in Postman
After confirming that API is working as expected, we move to frontend part. We'll create a simple HTML form named create.html inside the resources/templates/event directory. This form allow users to input event details and submit them to the backend. The backend will process the form data and save the event information in the PostgreSQL database.
resources / templates / event / create.html
<form class="pt-6 space-y-4" th:action="@{/event/create}" th:object="${event}" method="post"> <div> <label for="eventName" class="block mb-2 text-sm font-medium text-gray-900"> Event Name <span class="text-red-400">*</span> </label> <input type="text" id="eventName" th:field="*{eventName}" required class="border-gray-300 shadow-sm bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 focus:outline-none"/> </div> <div> <label for="eventDescription" class="block mb-2 text-sm font-medium text-gray-900"> Event Description <span class="text-red-400">*</span> </label> <textarea id="eventDescription" th:field="*{eventDescription}" required rows="4" class="border-gray-300 shadow-sm bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 focus:outline-none"></textarea> </div> <div> <label for="eventLocation" class="block mb-2 text-sm font-medium text-gray-900"> Event Location <span class="text-red-400">*</span> </label> <input type="text" id="eventLocation" th:field="*{eventLocation}" required class="border-gray-300 shadow-sm bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 focus:outline-none"/> </div> <div class="grid md:grid-cols-2 md:gap-6"> <div class="relative z-0 w-full group"> <label for="eventStartDatetime" class="block mb-2 text-sm font-medium text-gray-900"> Event Start Date & Time <span class="text-red-400">*</span> </label> <input type="datetime-local" id="eventStartDatetime" th:field="*{eventStartDatetime}" required class="border-gray-300 shadow-sm bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 focus:outline-none"/> </div> <div class="relative z-0 w-full group pt-4 sm:pt-0"> <label for="eventEndDatetime" class="block mb-2 text-sm font-medium text-gray-900"> Event End Date & Time <span class="text-red-400">*</span> </label> <input type="datetime-local" id="eventEndDatetime" th:field="*{eventEndDatetime}" required class="border-gray-300 shadow-sm bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 focus:outline-none"/> </div> </div> <div class="grid md:grid-cols-2 md:gap-6"> <div class="relative z-0 w-full group"> <label for="registrationStartDatetime" class="block mb-2 text-sm font-medium text-gray-900"> Registration Start Date & Time <span class="text-red-400">*</span> </label> <input type="datetime-local" id="registrationStartDatetime" th:field="*{registrationStartDatetime}" required class="border-gray-300 shadow-sm bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 focus:outline-none"/> </div> <div class="relative z-0 w-full group pt-4 sm:pt-0"> <label for="registrationEndDatetime" class="block mb-2 text-sm font-medium text-gray-900"> Registration End Date & Time <span class="text-red-400">*</span> </label> <input type="datetime-local" id="registrationEndDatetime" th:field="*{registrationEndDatetime}" required class="border-gray-300 shadow-sm bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 focus:outline-none"/> </div> </div> <button type="submit" class="py-3 px-5 text-sm font-medium text-center text-white rounded-lg bg-gray-600 sm:w-fit focus:outline-none"> Save </button> </form>
You can integrate google maps API to search for event location and any date picker library to select registration and event date and time. To keep things simple, we are using a basic HTML form. You can refer to this GitHub repository for the complete implementation and reference, https://github.com/MahediSabuj/event-registration/tree/v1.0.0
Hopefully, you have successfully created the event creation functionality using Spring Boot and PostgreSQL. In the next article, we will implement the user registration and login functionality using Spring Security.
Write your Comment