Medicare React Frontend Setup with Vite, Bootstrap & Routing

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
  • By the end of this step, we will have:
Frontend running successfully
C:\MEDICAREPROJECT\medicare-backend

Go to Your Project Folder

1

  • You currently have:
  • We want frontend and backend side by side:
C:\MEDICARE PROJECT
│
├── medicare-backend
└── medicare-frontend
  • Open CMD/PowerShell/terminal:
npm create vite@latest medicare-frontend -- --template react

Create React Project

2

  • Run:
cd "C:\MEDICARE PROJECT"
npm create vite@latest medicare-frontend -- --template react
  • Run:
npm create vite@latest medicare-frontend -- --template react
  • Run:
npm install axios bootstrap react-router

Install Our Required Dependencies

3

  • Run:
  • Vite currently requires a modern Node version; its docs list Node 20.19+ or 22.12+.
  • If Vite gives you a Node-version warning, upgrade Node before continuing.  
  • Vite currently requires a modern Node version; its docs list Node 20.19+ or 22.12+.
axios → Spring Boot API communication
bootstrap → Styling
react-router → Frontend routing
  • Run:
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?

  • This: unique = true on: appointment_id means: Appointment 10 --> Prescription 5
  • But not:
Appointment 10
 ├── Prescription 5
 └── Prescription 8 
  • So one appointment gets at most one prescription.

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?

  • We're managing medicines through: Prescription and: cascade = CascadeType.ALL
  • So creating: PrescriptionMedicineRepository would currently add unnecessary complexity.

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);
    }
}
  • We'll use this when: Appointment already has a prescription

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?

  • During creation:
Prescription
    +
Medicine 1
    +
Medicine 2
    +
Medicine 3
  • should be one operation.
  • Conceptually:
BEGIN
save prescription
save medicine 1
save medicine 2
save medicine 3
SUCCESS
 ↓
COMMIT
  • If something fails:ROLLBACK so we don't leave partially saved prescription data.

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

  • Add these imports :
import com.medicare.exception.PrescriptionConflictException;
import com.medicare.exception.PrescriptionNotFoundException;
  • Then add :
@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);
}
  • And:
@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

  • Inside: .authorizeHttpRequests(auth -> ...) add these rules before:   anyRequest().denyAll()
// 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

  • Hibernate should create:
users
doctor_profiles
appointments
prescriptions
prescription_medicines
prescriptions
prescription_medicines
  • Check: SHOW TABLES;
  • You should now have approximately:

Check prescriptions

13

  • Run:
DESC prescriptions;
  • Expected roughly:
id
appointment_id
diagnosis
doctor_notes
created_at

Check Medicine Table

14

  • Run:
DESC prescription_medicines;
  • Expected roughly:
id
prescription_id
medicine_name
dosage
frequency
duration

Prepare Appointment for Testing

15

  • Suppose :
Patient: Rahul
Doctor: Dr. Ananya
Appointment: ID = 5
  • Doctor first completes it:
PATCH /api/appointments/5/complete
Authorization: Bearer <doctor-token>
  • Expected: 200 OK
  • Appointment: COMPLETED

Doctor Creates Prescription

16

  • Use the assigned doctor's token:
POST /api/prescriptions
  • Body :
{
    "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"
        }
    ]
}
  • Expected: 201 CREATED

Wrong Doctor Attempts Prescription

17

  • Appointment belongs to: Dr. Ananya but Dr. Rohan attempts to prescribe.

  • Expected: 403 FORBIDDEN
  • Patient token:

Patient Attempts Prescription

18

POST /api/prescriptions
  • Expected: 403 FORBIDDEN
  • The request is rejected by Spring Security before the prescription service is used.

Duplicate Prescription

19

  • Doctor creates one prescription successfully.
  • Then tries again: POST /api/prescriptions for the same appointment.
  • Expected: 409 CONFLICT
  • Response :
{
    "status": 409,
    "message": "Prescription already exists for     this appointment"
}

Patient Views Prescription

20

  • Patient JWT:
GET /api/prescriptions/appointment/5
  • Expected: 200 OK with the prescription.
  • This will eventually power the React: View Prescription button required in the project.

Another Patient Tries Accessing It

21

  • Another Patient :
GET /api/prescriptions/appointment/5
  • Expected: 403 FORBIDDEN

Assigned Doctor Views It

22

  • Assigned doctor:
GET /api/prescriptions/appointment/5
  • Expected: 200 OK
  • Different doctor: 403 FORBIDDEN

Patient Gets All Own Prescriptions

23

GET /api/prescriptions/my
Authorization: Bearer <patient-token>

Prescription Doesn't Exist

24

GET /api/prescriptions/appointment/100
  • If Appointment 100 exists but doesn't have a prescription: 404 NOT FOUND
  • Message: Prescription not found for appointment id: 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 branchName

Next-Lab Preparation

Module:

1) React Basics & Backend Integration (Spring Boot)​

SpringBoot Lab - 13

By Content ITV

SpringBoot Lab - 13

  • 58