A full-stack web application for managing doctors and receptionists at the Westminster Health Centre.
| Technology | Purpose |
|---|---|
| Java 17 | Language |
| Spring Boot 3.2 | Application framework |
| Spring Data JPA + Hibernate | ORM and database access |
| PostgreSQL | Production database |
| Bean Validation | Request validation |
| SpringDoc OpenAPI | Auto-generated Swagger UI |
| JUnit 5 + Mockito | Unit and integration testing |
| H2 | In-memory database for tests |
| Technology | Purpose |
|---|---|
| React 18 + TypeScript | UI framework |
| Vite | Build tool |
| Tailwind CSS + shadcn/ui | Styling and components |
| React Hook Form + Zod | Form handling and validation |
| Lucide React | Icons |
- Add, edit and remove doctors and receptionists
- Live search — filter staff by name, surname or staff ID
- Stats dashboard — total staff, doctors, receptionists and remaining capacity
- Role badges — colour-coded Doctor / Receptionist labels
- Form validation — client-side (Zod) and server-side (Bean Validation) with consistent error messages
- Sample data — 8 staff members seeded on first startup
- Swagger UI — interactive API documentation at
/swagger-ui - REST API — full CRUD with proper HTTP status codes and a consistent error envelope
westminster-health-centre/
├── westminster-health-backend/ Spring Boot REST API
│ ├── src/main/java/com/westminster/healthcentre/
│ │ ├── config/ CORS, OpenAPI, DataSeeder
│ │ ├── controller/ REST endpoints
│ │ ├── dto/ Request / response records
│ │ ├── exception/ Typed exceptions + global handler
│ │ ├── model/ JPA entities (StaffMember, Doctor, Receptionist)
│ │ ├── repository/ Spring Data JPA repository
│ │ └── service/ Business logic
│ └── src/test/ JUnit 5 unit + integration tests
│
├── westminster-health-frontend/ React frontend
│ └── src/
│ ├── api/ Fetch calls (staffApi.ts)
│ ├── types/ TypeScript interfaces
│ ├── hooks/ Custom hooks (useHealthCentreData)
│ └── components/ UI components
│
├── assets/ Screenshots
├── .gitignore
└── README.md
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/staff |
List all staff (optional ?search=) |
GET |
/api/staff/stats |
Counts and remaining capacity |
GET |
/api/staff/{staffId} |
Get a single staff member |
POST |
/api/staff/doctors |
Add a new doctor |
POST |
/api/staff/receptionists |
Add a new receptionist |
PUT |
/api/staff/doctors/{staffId} |
Update an existing doctor |
PUT |
/api/staff/receptionists/{staffId} |
Update an existing receptionist |
DELETE |
/api/staff/{staffId} |
Remove a staff member |
All errors return a consistent JSON envelope:
{
"status": 404,
"message": "No staff member found with ID 'DR9999'.",
"timestamp": "2025-01-15T10:30:00Z"
}- Java 17+
- Maven 3.8+
- Node.js 18+
- PostgreSQL (or Docker)
Using Docker:
docker run --name healthcentre-db \
-e POSTGRES_DB=healthcentre \
-e POSTGRES_PASSWORD=password \
-p 5432:5432 -d postgres:16Using an existing PostgreSQL installation:
psql -U postgres -c "CREATE DATABASE healthcentre;"Create westminster-health-backend/.env:
DATABASE_URL=jdbc:postgresql://localhost:5432/healthcentre
DB_USERNAME=postgres
DB_PASSWORD=your_password
cd westminster-health-backend
mvn spring-boot:runAPI runs on http://localhost:8080.
Swagger UI at http://localhost:8080/swagger-ui.
cd westminster-health-frontend
npm install
cp .env.example .env.local # already points to localhost:8080
npm run devFrontend runs on http://localhost:5173.
cd westminster-health-backend
mvn testTests use an H2 in-memory database — no PostgreSQL needed. The suite includes:
- Unit tests (
StaffServiceImplTest) — Mockito mocks, no Spring context, fast - Integration tests (
StaffControllerIntegrationTest) — full Spring context, MockMvc hitting real endpoints against H2
Doctor and Receptionist share most fields and the total row count is small (≤50). SINGLE_TABLE avoids joins, keeps queries fast, and the schema simple. A discriminator column (staff_type) tells Hibernate which subtype to instantiate on read.
StaffController depends on StaffService (the abstraction), not StaffServiceImpl (the implementation). This means unit tests can mock the interface without needing Spring or a database — keeping them fast and focused on business logic.
JPA entities carry database concerns (lazy-loading proxies, discriminator columns, internal IDs). Exposing them from the API leaks implementation details. DTOs are plain Java records containing exactly what the API consumer needs.
Each exception (StaffNotFoundException, DuplicateStaffIdException, StaffLimitReachedException) maps to a specific HTTP status in GlobalExceptionHandler. Controllers contain no try/catch blocks — error handling is centralised.
The application state is simple — a list of staff and a stats summary owned by App, passed down as props. useState, useEffect, and useCallback are sufficient. Adding Redux or TanStack Query would introduce complexity without solving a real problem at this scale.
The dataset is capped at 50 records. Filtering an already-fetched array in memory is instant and avoids unnecessary network requests. A server-side search endpoint (?search=) is still available for use from the Swagger UI or external clients.
- Push to GitHub
- Create a new Railway project → Deploy from GitHub repo
- Add a PostgreSQL plugin — Railway injects
DATABASE_URLautomatically - Set environment variables:
DB_USERNAME=postgres DB_PASSWORD=<from Railway PostgreSQL plugin> CORS_ALLOWED_ORIGINS=https://your-frontend.vercel.app
- Import the GitHub repo on Vercel
- Set root directory to
westminster-health-frontend - Add environment variable:
VITE_API_URL=https://your-api.railway.app - Deploy — Vercel detects Vite automatically
