Tutorial 5: Controller & UI¶
Pages: 2
📖 Page 1: Controller¶
Create PatientController¶
Create src/main/java/com/healthcare/pms/controller/PatientController.java:
package com.healthcare.pms.controller;
import com.healthcare.pms.dto.PatientDTO;
import com.healthcare.pms.service.PatientService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/patients")
@RequiredArgsConstructor
public class PatientController {
private final PatientService patientService;
// LIST - GET /patients
@GetMapping
public String listPatients(@RequestParam(required = false) String search,
@RequestParam(required = false) String gender,
Model model) {
List<PatientDTO> patients;
if (search != null && !search.isEmpty()) {
patients = patientService.searchPatients(search);
} else {
patients = patientService.filterPatients(gender, null, null);
}
model.addAttribute("patients", patients);
model.addAttribute("search", search);
model.addAttribute("gender", gender);
return "patients/list";
}
// NEW FORM - GET /patients/new
@GetMapping("/new")
public String showCreateForm(Model model) {
model.addAttribute("patient", new PatientDTO());
return "patients/form";
}
// CREATE - POST /patients
@PostMapping
public String createPatient(@Valid @ModelAttribute("patient") PatientDTO dto,
BindingResult result, Model model) {
if (result.hasErrors()) {
return "patients/form";
}
PatientDTO created = patientService.createPatient(dto);
return "redirect:/patients/" + created.getId();
}
// VIEW - GET /patients/{id}
@GetMapping("/{id}")
public String viewPatient(@PathVariable String id, Model model) {
PatientDTO patient = patientService.getPatientById(id);
model.addAttribute("patient", patient);
return "patients/view";
}
// EDIT FORM - GET /patients/{id}/edit
@GetMapping("/{id}/edit")
public String showEditForm(@PathVariable String id, Model model) {
PatientDTO patient = patientService.getPatientById(id);
model.addAttribute("patient", patient);
return "patients/form";
}
// UPDATE - POST /patients/{id}
@PostMapping("/{id}")
public String updatePatient(@PathVariable String id,
@Valid @ModelAttribute("patient") PatientDTO dto,
BindingResult result) {
if (result.hasErrors()) {
return "patients/form";
}
patientService.updatePatient(id, dto);
return "redirect:/patients/" + id;
}
// DELETE - POST /patients/{id}/delete
@PostMapping("/{id}/delete")
public String deletePatient(@PathVariable String id) {
patientService.deletePatient(id);
return "redirect:/patients";
}
}
📖 Page 2: Thymeleaf Templates¶
List Page¶
Create src/main/resources/templates/patients/list.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Patients</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<h1>Patients</h1>
<!-- Search & Filter -->
<form class="row mb-3" method="get">
<div class="col-md-6">
<input type="text" name="search" class="form-control"
placeholder="Search by name" th:value="${search}">
</div>
<div class="col-md-3">
<select name="gender" class="form-control">
<option value="">All Genders</option>
<option value="male" th:selected="${gender == 'male'}">Male</option>
<option value="female" th:selected="${gender == 'female'}">Female</option>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary">Search</button>
<a href="/patients/new" class="btn btn-success">+ New</a>
</div>
</form>
<!-- Table -->
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Age</th>
<th>Gender</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr th:each="patient, stat : ${patients}">
<td th:text="${stat.count}"></td>
<td>
<a th:href="@{/patients/{id}(id=${patient.id})}"
th:text="${patient.fullName}"></a>
</td>
<td th:text="${#temporals.year(#temporals.createNow()) -
#temporals.year(patient.birthDate)}"></td>
<td th:text="${patient.gender}"></td>
<td th:text="${patient.email}"></td>
<td>
<a th:href="@{/patients/{id}/edit(id=${patient.id})}"
class="btn btn-sm btn-warning">Edit</a>
<form th:action="@{/patients/{id}/delete(id=${patient.id})}"
method="post" style="display:inline">
<button class="btn btn-sm btn-danger"
onclick="return confirm('Delete?')">Delete</button>
</form>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
Form Page (Create/Edit)¶
Create src/main/resources/templates/patients/form.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Patient Form</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<h1 th:text="${patient.id == null ? 'New Patient' : 'Edit Patient'}"></h1>
<form th:object="${patient}"
th:action="${patient.id == null ? '/patients' : '/patients/' + patient.id}"
method="post">
<div class="row">
<div class="col-md-6 mb-3">
<label>First Name *</label>
<input type="text" th:field="*{firstName}" class="form-control">
<span th:if="${#fields.hasErrors('firstName')}"
th:errors="*{firstName}" class="text-danger"></span>
</div>
<div class="col-md-6 mb-3">
<label>Last Name *</label>
<input type="text" th:field="*{lastName}" class="form-control">
<span th:if="${#fields.hasErrors('lastName')}"
th:errors="*{lastName}" class="text-danger"></span>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label>Birth Date *</label>
<input type="date" th:field="*{birthDate}" class="form-control">
</div>
<div class="col-md-6 mb-3">
<label>Gender *</label>
<select th:field="*{gender}" class="form-control">
<option value="male">Male</option>
<option value="female">Female</option>
<option value="other">Other</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label>Email</label>
<input type="email" th:field="*{email}" class="form-control">
</div>
<div class="col-md-6 mb-3">
<label>Phone</label>
<input type="text" th:field="*{phone}" class="form-control">
</div>
</div>
<button type="submit" class="btn btn-primary">Save</button>
<a href="/patients" class="btn btn-secondary">Cancel</a>
</form>
</div>
</body>
</html>
View Page¶
Create src/main/resources/templates/patients/view.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Patient Details</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<h1 th:text="${patient.fullName}"></h1>
<div class="card">
<div class="card-body">
<p><strong>ID:</strong> <span th:text="${patient.id}"></span></p>
<p><strong>Birth Date:</strong>
<span th:text="${#temporals.format(patient.birthDate, 'MMM dd, yyyy')}"></span>
</p>
<p><strong>Gender:</strong> <span th:text="${patient.gender}"></span></p>
<p><strong>Email:</strong> <span th:text="${patient.email}"></span></p>
<p><strong>Phone:</strong> <span th:text="${patient.phone}"></span></p>
<p><strong>Status:</strong>
<span th:text="${patient.active ? 'Active' : 'Inactive'}"></span>
</p>
</div>
</div>
<div class="mt-3">
<a th:href="@{/patients/{id}/edit(id=${patient.id})}"
class="btn btn-warning">Edit</a>
<a href="/patients" class="btn btn-secondary">Back to List</a>
</div>
</div>
</body>
</html>
✅ Web Interface Complete¶
- PatientController with all endpoints
- List page with search/filter
- Form page (create/edit)
- View page (details)
- Bootstrap styling
Test: http://localhost:8081/patients
🚀 Next¶
Basic CRUD working! Now add advanced filters.