Business Scenario
In this lab, we will begin developing the Medicare React frontend by setting up a React application using Vite and integrating Bootstrap, Axios, and React Router.
Pre-Lab Preparation
Module:
1) React Basics & Backend Integration (Spring Boot)
React + Vite project
Bootstrap
Axios
createBrowserRouter
Clean folder structure
Central API configuration
Environment variable for backend URL
Basic routes
Placeholder pages
Frontend running successfully
C:\MEDICAREPROJECT\medicare-backend
Go to Your Project Folder
1
C:\MEDICARE PROJECT │ ├── medicare-backend └── medicare-frontend
npm create vite@latest medicare-frontend -- --template reactCreate React Project
2
cd "C:\MEDICARE PROJECT"
npm create vite@latest medicare-frontend -- --template reactnpm create vite@latest medicare-frontend -- --template reactnpm install axios bootstrap react-router
Install Our Required Dependencies
3
axios → Spring Boot API communication bootstrap → Styling react-router → Frontend routing
package com.medicare.entity;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
@Entity
@Table(name = "prescriptions")
public class Prescription {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "appointment_id",nullable = false,unique = true) private Appointment appointment;
@Column(nullable = false,columnDefinition = "TEXT")
private String diagnosis;
@Column(name = "doctor_notes",columnDefinition = "TEXT")
private String doctorNotes;
@OneToMany(mappedBy = "prescription",cascade =
CascadeType.ALL,orphanRemoval = true)
private List<PrescriptionMedicine> medicines = new ArrayList<>();
@Column(name = "created_at",nullable = false,updatable = false)
private LocalDateTime createdAt = LocalDateTime.now();
public Prescription() {
}
public Long getId() {
return id;
}
public Appointment getAppointment() {
return appointment;
}
public void setAppointment(Appointment appointment) {
this.appointment = appointment;
} public String getDiagnosis() {
return diagnosis;
}
public void setDiagnosis(String diagnosis) {
this.diagnosis = diagnosis;
}
public String getDoctorNotes() {
return doctorNotes;
}
public void setDoctorNotes(String doctorNotes) {
this.doctorNotes = doctorNotes;
}
public List<PrescriptionMedicine> getMedicines() {
return medicines;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void addMedicine(PrescriptionMedicine medicine) {
medicines.add(medicine);
medicine.setPrescription(this);
}
}Why unique = true?
Appointment 10 ├── Prescription 5 └── Prescription 8
DTO #1 — MedicineDto
2
dto/ └─ MedicineDto.java
package com.medicare.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class MedicineDto {
@NotBlank(message = "Medicine name is required")
@Size(max = 150)
private String medicineName;
@NotBlank(message = "Dosage is required")
@Size(max = 100)
private String dosage;
@NotBlank(message = "Frequency is required")
@Size(max = 100)
private String frequency;
@NotBlank(message = "Duration is required")
@Size(max = 100)
private String duration;
public MedicineDto() {
} public MedicineDto(
String medicineName,
String dosage,
String frequency,
String duration) {
this.medicineName = medicineName;
this.dosage = dosage;
this.frequency = frequency;
this.duration = duration;
}
public String getMedicineName() {
return medicineName;
}
public void setMedicineName(
String medicineName) {
this.medicineName = medicineName;
}
public String getDosage() {
return dosage;
}
public void setDosage(
String dosage) {
this.dosage = dosage;
} public String getFrequency() {
return frequency;
}
public void setFrequency(
String frequency) {
this.frequency = frequency;
}
public String getDuration() {
return duration;
}
public void setDuration(
String duration) {
this.duration = duration;
}
}This one DTO is used for both:
Prescription Request Prescription Response
DTO #2 — PrescriptionRequest
3
dto/ └─ PrescriptionRequest.java
package com.medicare.dto;
import java.util.List;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class PrescriptionRequest {
@NotNull(message = "Appointment id is required")
private Long appointmentId;
@NotBlank(message = "Diagnosis is required")
@Size(max = 2000,message = "Diagnosis is too long")
private String diagnosis; @Size(max = 3000,message = "Doctor notes are too long")
private String doctorNotes;
@NotEmpty(message = "At least one medicine is required")
@Valid
private List<MedicineDto> medicines;
public Long getAppointmentId() {
return appointmentId;
}
public void setAppointmentId(
Long appointmentId) {
this.appointmentId = appointmentId;
}
public String getDiagnosis() {
return diagnosis;
}
public void setDiagnosis(String diagnosis) {
this.diagnosis = diagnosis;
}
public String getDoctorNotes() {
return doctorNotes;
} public void setDoctorNotes(
String doctorNotes) {
this.doctorNotes = doctorNotes;
}
public List<MedicineDto> getMedicines() {
return medicines;
}
public void setMedicines(
List<MedicineDto> medicines) {
this.medicines = medicines;
}
}DTO #3 — PrescriptionResponse
4
dto/ └─PrescriptionResponse.java
package com.medicare.dto;import java.time.LocalDateTime;
import java.util.List;
public class PrescriptionResponse {
private Long id;
private Long appointmentId;
private Long doctorId;
private String doctorName;
private Long patientId;
private String patientName;
private String diagnosis;
private String doctorNotes;
private List<MedicineDto> medicines;
private LocalDateTime createdAt;
public PrescriptionResponse(
Long id,
Long appointmentId,
Long doctorId,
String doctorName,
Long patientId,
String patientName,
String diagnosis,
String doctorNotes,
List<MedicineDto> medicines,
LocalDateTime createdAt) {
this.id = id;
this.appointmentId = appointmentId;
this.doctorId = doctorId; this.doctorName = doctorName;
this.patientId = patientId;
this.patientName = patientName;
this.diagnosis = diagnosis;
this.doctorNotes = doctorNotes;
this.medicines = medicines;
this.createdAt = createdAt;
}
public Long getId() {
return id;
}
public Long getAppointmentId() {
return appointmentId;
}
public Long getDoctorId() {
return doctorId;
}
public String getDoctorName() {
return doctorName;
}
public Long getPatientId() {
return patientId;
} public String getPatientName() {
return patientName;
}
public String getDiagnosis() {
return diagnosis;
}
public String getDoctorNotes() {
return doctorNotes;
}
public List<MedicineDto> getMedicines() {
return medicines;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
}Create PrescriptionRepository
5
repository/ └─PrescriptionRepository.java
package com.medicare.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import com.medicare.entity.Prescription;
public interface PrescriptionRepository extends JpaRepository<Prescription, Long> {
boolean existsByAppointmentId(Long appointmentId);
Optional<Prescription> findByAppointmentId(Long appointmentId);
List<Prescription> findByAppointment_Patient_Id(
Long patientId
);
}Do We Need PrescriptionMedicineRepository?
Create PrescriptionNotFoundException
6
exception/ └─PrescriptionNotFoundException.java
package com.medicare.exception;
public class PrescriptionNotFoundException extends RuntimeException {
public PrescriptionNotFoundException(String message) {
super(message);
}
}Create PrescriptionConflictException
7
exception/ └─PrescriptionConflictException.java
package com.medicare.exception;
public class PrescriptionConflictException
extends RuntimeException {
public PrescriptionConflictException(
String message) {
super(message);
}
}Create PrescriptionService
8
service/ └─PrescriptionService.java
package com.medicare.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.medicare.dto.MedicineDto;
import com.medicare.dto.PrescriptionRequest;
import com.medicare.dto.PrescriptionResponse;
import com.medicare.entity.Appointment;
import com.medicare.entity.AppointmentStatus;
import com.medicare.entity.DoctorProfile;
import com.medicare.entity.Prescription;
import com.medicare.entity.PrescriptionMedicine;import com.medicare.entity.Role;
import com.medicare.entity.User;
import com.medicare.exception.AppointmentNotFoundException;
import com.medicare.exception.DoctorNotFoundException;
import com.medicare.exception.PrescriptionConflictException;
import com.medicare.exception.PrescriptionNotFoundException;
import com.medicare.exception.UserNotFoundException;
import com.medicare.repository.AppointmentRepository;
import com.medicare.repository.DoctorProfileRepository;
import com.medicare.repository.PrescriptionRepository;
import com.medicare.repository.UserRepository;
@Service
public class PrescriptionService {
private final PrescriptionRepository prescriptionRepository;
private final AppointmentRepository appointmentRepository;
private final UserRepository userRepository;
private final DoctorProfileRepository doctorRepository;
public PrescriptionService(
PrescriptionRepository prescriptionRepository,
AppointmentRepository appointmentRepository,
UserRepository userRepository,
DoctorProfileRepository doctorRepository) {
this.prescriptionRepository = prescriptionRepository;
this.appointmentRepository = appointmentRepository;
this.userRepository = userRepository;
this.doctorRepository = doctorRepository;
} // DOCTOR - CREATE PRESCRIPTION
@Transactional
@PreAuthorize("hasRole('DOCTOR')")
public PrescriptionResponse createPrescription(
PrescriptionRequest request, String email) {
User user = findUserByEmail(email);
DoctorProfile doctor = findDoctorByUserId(user.getId());
Appointment appointment = findAppointmentById(
request.getAppointmentId());
// Only the assigned doctor can create the prescription.
if (!appointment.getDoctor().getId().equals(doctor.getId())) {
throw new AccessDeniedException(
"You cannot create a prescription for this appointment");
}
// Prescription can only be created after consultation is completed.
if (appointment.getAppointmentStatus() != AppointmentStatus.COMPLETED) {
throw new IllegalArgumentException(
"Prescription can only be created for a completed appointment");
}
// Only one prescription is allowed per appointment.
if (prescriptionRepository.existsByAppointmentId(appointment.getId())) {
throw new PrescriptionConflictException(
"Prescription already exists for this appointment");
}
Prescription prescription = new Prescription();
prescription.setAppointment(appointment); prescription.setDiagnosis(request.getDiagnosis());
prescription.setDoctorNotes(request.getDoctorNotes());
for (MedicineDto medicineDto : request.getMedicines()) {
PrescriptionMedicine medicine = new PrescriptionMedicine();
medicine.setMedicineName(medicineDto.getMedicineName());
medicine.setDosage(medicineDto.getDosage());
medicine.setFrequency(medicineDto.getFrequency());
medicine.setDuration(medicineDto.getDuration());
prescription.addMedicine(medicine);
}
Prescription savedPrescription = prescriptionRepository
.save(prescription);
return mapToPrescriptionResponse(savedPrescription);
}
// VIEW PRESCRIPTION FOR APPOINTMENT
@Transactional(readOnly = true)
@PreAuthorize("hasAnyRole('PATIENT', 'DOCTOR')")
public PrescriptionResponse getPrescriptionByAppointment(
Long appointmentId, String email) {
Prescription prescription = prescriptionRepository
.findByAppointmentId(appointmentId)
.orElseThrow(() -> new PrescriptionNotFoundException(
"Prescription not found for appointment id: "
+ appointmentId));
User user = findUserByEmail(email);
verifyPrescriptionAccess(prescription, user); return mapToPrescriptionResponse(prescription);
}
// PATIENT - VIEW OWN PRESCRIPTIONS
@Transactional(readOnly = true)
@PreAuthorize("hasRole('PATIENT')")
public List<PrescriptionResponse> getMyPrescriptions(String email) {
User patient = findUserByEmail(email);
List<Prescription> prescriptions =
prescriptionRepository.findByAppointment_Patient_Id(
patient.getId());
List<PrescriptionResponse> responses = new ArrayList<>();
for (Prescription prescription : prescriptions) {
responses.add(mapToPrescriptionResponse(prescription));
}
return responses;
}
// OWNERSHIP CHECK
private void verifyPrescriptionAccess(
Prescription prescription, User user) {
Appointment appointment = prescription.getAppointment();
if (user.getRole() == Role.PATIENT) {
if (!appointment.getPatient().getId().equals(user.getId())) {
throw new AccessDeniedException("
You cannot access this prescription");
} return;
}
if (user.getRole() == Role.DOCTOR) {
DoctorProfile doctor = findDoctorByUserId(user.getId());
if (!appointment.getDoctor().getId().equals(doctor.getId())) {
throw new AccessDeniedException(
"You cannot access this prescription");
}
}
}
// HELPER METHODS
private User findUserByEmail(String email) {
return userRepository.findByEmailIgnoreCase(email)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
private DoctorProfile findDoctorByUserId(Long userId) {
return doctorRepository.findByUserId(userId)
.orElseThrow(() ->
new DoctorNotFoundException("Doctor profile not found"));
}
private Appointment findAppointmentById(Long appointmentId) {
return appointmentRepository.findById(appointmentId)
.orElseThrow(() -> new AppointmentNotFoundException(
"Appointment not found with id: " + appointmentId));
} // RESPONSE MAPPING
private PrescriptionResponse mapToPrescriptionResponse(
Prescription prescription) {
List<MedicineDto> medicines = new ArrayList<>();
for (PrescriptionMedicine medicine : prescription.getMedicines()) {
MedicineDto medicineDto = new MedicineDto(
medicine.getMedicineName(),
medicine.getDosage(),
medicine.getFrequency(),
medicine.getDuration());
medicines.add(medicineDto);
}
Appointment appointment = prescription.getAppointment();
return new PrescriptionResponse(
prescription.getId(),
appointment.getId(),
appointment.getDoctor().getId(),
appointment.getDoctor().getUser().getName(),
appointment.getPatient().getId(),
appointment.getPatient().getName(),
prescription.getDiagnosis(),
prescription.getDoctorNotes(),
medicines, prescription.getCreatedAt());
}
}Why @Transactional?
Prescription + Medicine 1 + Medicine 2 + Medicine 3
BEGIN save prescription save medicine 1 save medicine 2 save medicine 3 SUCCESS ↓ COMMIT
Create PrescriptionController
9
controller/ └─PrescriptionController.java
package com.medicare.controller;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.medicare.dto.PrescriptionRequest;
import com.medicare.dto.PrescriptionResponse;
import com.medicare.service.PrescriptionService;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/prescriptions")
public class PrescriptionController {
private final PrescriptionService prescriptionService;
public PrescriptionController(PrescriptionService prescriptionService) {
this.prescriptionService = prescriptionService;
} // DOCTOR - CREATE PRESCRIPTION
// -------------------------------------------------
@PostMapping
public ResponseEntity<PrescriptionResponse>
createPrescription(
@Valid
@RequestBody
PrescriptionRequest request,
Authentication authentication) {
PrescriptionResponse response = prescriptionService
.createPrescription(
request,
authentication.getName()
);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
// PATIENT / DOCTOR - VIEW BY APPOINTMENT
// -------------------------------------------------
@GetMapping("/appointment/{appointmentId}")
public ResponseEntity<PrescriptionResponse>
getPrescriptionByAppointment(
@PathVariable Long appointmentId,
Authentication authentication) { return ResponseEntity.ok(
prescriptionService
.getPrescriptionByAppointment(
appointmentId,
authentication.getName()
)
);
}
// -------------------------------------------------
// PATIENT - VIEW OWN PRESCRIPTIONS
// -------------------------------------------------
@GetMapping("/my")
public ResponseEntity<List<PrescriptionResponse>>
getMyPrescriptions(
Authentication authentication) {
return ResponseEntity.ok(
prescriptionService
.getMyPrescriptions(
authentication.getName()
)
);
}
}Update GlobalExceptionHandler
10
import com.medicare.exception.PrescriptionConflictException;
import com.medicare.exception.PrescriptionNotFoundException;@ExceptionHandler(PrescriptionNotFoundException.class)
public ResponseEntity<ErrorResponse>
handlePrescriptionNotFound(
PrescriptionNotFoundException exception,
HttpServletRequest request) {
ErrorResponse errorResponse = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.NOT_FOUND.value(),
exception.getMessage(), request.getRequestURI()
);
return ResponseEntity .status(HttpStatus.NOT_FOUND)
.body(errorResponse);
}@ExceptionHandler(PrescriptionConflictException.class)
public ResponseEntity<ErrorResponse>
handlePrescriptionConflict( PrescriptionConflictException exception,
HttpServletRequest request) {
ErrorResponse errorResponse = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.CONFLICT.value(),
exception.getMessage(),
request.getRequestURI()
);
return ResponseEntity.status(HttpStatus.CONFLICT).body(errorResponse);
}Update SecurityConfig
11
// Doctor creates prescription
.requestMatchers(HttpMethod.POST,"/api/prescriptions")
.hasRole("DOCTOR")
// Patient views all own prescriptions
.requestMatchers(HttpMethod.GET,"/api/prescriptions/my")
.hasRole("PATIENT")
// Patient or assigned doctor can view
// prescription for an appointment
.requestMatchers(HttpMethod.GET,"/api/prescriptions/appointment/*")
.hasAnyRole("PATIENT","DOCTOR")Restart Application
12
users
doctor_profiles
appointments
prescriptions
prescription_medicinesprescriptions prescription_medicines
Check prescriptions
13
DESC prescriptions;id appointment_id diagnosis doctor_notes created_at
Check Medicine Table
14
DESC prescription_medicines;id prescription_id medicine_name dosage frequency duration
Prepare Appointment for Testing
15
Patient: Rahul Doctor: Dr. Ananya Appointment: ID = 5
PATCH /api/appointments/5/complete Authorization: Bearer <doctor-token>
Doctor Creates Prescription
16
POST /api/prescriptions
{
"appointmentId": 5,
"diagnosis": "Viral fever with mild dehydration",
"doctorNotes": "Take adequate rest and drink plenty of fluids.",
"medicines": [
{ "medicineName": "Paracetamol",
"dosage": "500 mg",
"frequency": "Twice daily",
"duration": "5 days"
},
{
"medicineName": "ORS",
"dosage": "1 sachet",
"frequency": "Twice daily",
"duration": "3 days"
}
]
}Wrong Doctor Attempts Prescription
17
Appointment belongs to: Dr. Ananya but Dr. Rohan attempts to prescribe.
Patient Attempts Prescription
18
POST /api/prescriptions
Duplicate Prescription
19
{
"status": 409,
"message": "Prescription already exists for this appointment"
}Patient Views Prescription
20
GET /api/prescriptions/appointment/5
Another Patient Tries Accessing It
21
GET /api/prescriptions/appointment/5
Assigned Doctor Views It
22
GET /api/prescriptions/appointment/5
Patient Gets All Own Prescriptions
23
GET /api/prescriptions/my Authorization: Bearer <patient-token>
Prescription Doesn't Exist
24
GET /api/prescriptions/appointment/100
We are done with this lab. The latest source code has been uploaded to GitHub. You can access the latest commit using the link below:
Great job!
You have successfully implemented the Medicare Prescription Management Module using Spring Boot, JPA, and DTOs.
Checkpoint
Git Push
git push origin branchNameNext-Lab Preparation
Module:
1) React Basics & Backend Integration (Spring Boot)