Skip to content

Latest commit

 

History

History
490 lines (376 loc) · 14.1 KB

File metadata and controls

490 lines (376 loc) · 14.1 KB

Clinic Appointment & Billing System

A full-stack Java/Spring Boot + React portfolio project that models a clinic appointment and billing workflow. The project is designed for Java/React job applications: it demonstrates API design, layered Spring Boot architecture, JWT authentication, role-based access control, Flyway migrations, Docker deployment, and a TypeScript React frontend.

This is not intended to be a real clinical or medical billing product.

What This Project Demonstrates

  • Java 21 and Spring Boot 3 REST API design.
  • Layered backend structure with controllers, DTOs, services, repositories, entities, and centralized exception handling.
  • Spring Security with BCrypt password hashing, JWT access tokens, and role-based access control.
  • PostgreSQL persistence with Flyway-managed schema migrations.
  • Business rules for invoices, payments, status transitions, and overpayment prevention.
  • React + Vite + TypeScript frontend with protected routes and role-aware navigation.
  • Production-style Docker Compose deployment with PostgreSQL, Spring Boot, and Nginx.
  • Swagger/OpenAPI documentation and CI-ready build/test workflows.

Screenshots

Screenshots should be added before sharing this repository publicly:

  • Dashboard: docs/screenshots/dashboard.png
  • Login: docs/screenshots/login.png
  • Invoices: docs/screenshots/invoices.png
  • Invoice detail: docs/screenshots/invoice-detail.png
  • Admin users: docs/screenshots/users.png

The directory is present at docs/screenshots/ with a .gitkeep placeholder.

Demo Accounts

The application seeds one admin user when the backend starts and no admin with that email exists.

Local default:

Email: admin@clinic.local
Password: admin12345
Role: ADMIN

Production example defaults from deploy/prod/.env.example:

Email: admin@clinic.local
Password: change_me_admin_password
Role: ADMIN

Change APP_ADMIN_EMAIL and APP_ADMIN_PASSWORD before using a persistent environment. The bootstrap password is only used when the admin user is first created.

Features

  • JWT login and /api/auth/me profile endpoint.
  • ADMIN-only user management.
  • Role-aware React navigation.
  • Patient and appointment workflows for ADMIN, RECEPTIONIST, and DOCTOR.
  • Service catalogue, invoices, and payments for ADMIN and BILLING.
  • Invoice statuses: DRAFT, ISSUED, PARTIALLY_PAID, PAID, CANCELLED.
  • Automatic invoice subtotal, amount paid, and balance due calculations.
  • Swagger/OpenAPI docs.
  • Dockerized local database and production stack.

Stack

  • Backend: Java 21, Spring Boot 3, Spring Security, Spring Web, Spring Data JPA, Validation, Flyway, Actuator, Springdoc OpenAPI
  • Frontend: React, TypeScript, Vite, React Router, Axios
  • Database: PostgreSQL
  • Local development: PostgreSQL in Docker, backend and frontend on the host
  • Production: PostgreSQL, Spring Boot API, and Nginx-served React build in Docker Compose

Architecture Overview

Browser
  -> React + Vite SPA
      -> Axios API client with Bearer token
          -> Spring Boot REST API
              -> Spring Security JWT/RBAC
              -> Service layer business rules
              -> Spring Data JPA repositories
                  -> PostgreSQL

Production deployment:

Browser
  -> Nginx frontend container
      -> static React files
      -> /api proxy
          -> Spring Boot backend container
              -> PostgreSQL container

More detail: docs/ARCHITECTURE.md

ERD

erDiagram
    PATIENTS ||--o{ APPOINTMENTS : schedules
    PATIENTS ||--o{ INVOICES : receives
    INVOICES ||--o{ INVOICE_ITEMS : contains
    INVOICES ||--o{ PAYMENTS : records
    SERVICE_ITEMS ||--o{ INVOICE_ITEMS : prices
    USERS ||--o{ ROLES : has

    PATIENTS {
        uuid id PK
        varchar first_name
        varchar last_name
        varchar email UK
        varchar phone
        date date_of_birth
        timestamptz created_at
        timestamptz updated_at
    }

    APPOINTMENTS {
        uuid id PK
        uuid patient_id FK
        timestamptz scheduled_at
        varchar reason
        varchar status
        timestamptz created_at
        timestamptz updated_at
    }

    SERVICE_ITEMS {
        uuid id PK
        varchar name UK
        varchar description
        numeric default_price
        boolean active
        timestamptz created_at
        timestamptz updated_at
    }

    INVOICES {
        uuid id PK
        uuid patient_id FK
        varchar invoice_number UK
        varchar status
        numeric subtotal
        numeric amount_paid
        numeric balance_due
        timestamptz issued_at
        timestamptz created_at
        timestamptz updated_at
    }

    INVOICE_ITEMS {
        uuid id PK
        uuid invoice_id FK
        uuid service_item_id FK
        varchar description
        integer quantity
        numeric unit_price
        numeric line_total
        timestamptz created_at
    }

    PAYMENTS {
        uuid id PK
        uuid invoice_id FK
        numeric amount
        varchar method
        varchar reference
        timestamptz paid_at
        timestamptz created_at
    }

    USERS {
        uuid id PK
        varchar email UK
        varchar full_name
        varchar password_hash
        boolean enabled
        timestamptz created_at
        timestamptz updated_at
    }

    ROLES {
        uuid user_id FK
        varchar role
    }
Loading

Project Structure

clinic-system/
  backend/
    src/main/java/com/example/clinic/
    src/main/resources/db/migration/
    pom.xml
    Dockerfile
    .env.example
  frontend/
    src/
    package.json
    vite.config.js
    Dockerfile
    nginx.conf
    .env.example
  deploy/
    local/docker-compose.yml
    prod/docker-compose.yml
    prod/.env.example
  docs/
    ARCHITECTURE.md
    screenshots/.gitkeep
  .github/workflows/ci.yml
  README.md

Prerequisites

  • Docker and Docker Compose
  • Java 21 for local backend development
  • Node.js 22 recommended for local frontend development
  • curl or wget if you use the included backend/mvnw Maven launcher

Quick Start In 3 Commands

From the repository root, run these in separate terminals because the backend and frontend commands stay attached:

docker compose -f deploy/local/docker-compose.yml up -d
(cd backend && cp -n .env.example .env && ./mvnw spring-boot:run -Dspring-boot.run.profiles=local)
(cd frontend && cp -n .env.example .env && npm ci && npm run dev)

Then open:

Login locally with admin@clinic.local / admin12345.

Local Setup

Start PostgreSQL only:

cd clinic-system
docker compose -f deploy/local/docker-compose.yml up -d

Run the backend:

cd backend
cp .env.example .env
./mvnw spring-boot:run -Dspring-boot.run.profiles=local

Run the frontend:

cd frontend
cp .env.example .env
npm ci
npm run dev

Local URLs:

Production-Style Docker Setup

Create a production env file:

cd clinic-system
cp deploy/prod/.env.example deploy/prod/.env

Edit deploy/prod/.env and set strong database, JWT, and admin bootstrap secrets. At minimum, change POSTGRES_PASSWORD, DB_PASSWORD, APP_JWT_SECRET, and APP_ADMIN_PASSWORD.

Build and start the stack:

docker compose -f deploy/prod/docker-compose.yml --env-file deploy/prod/.env up -d --build

Production-style URLs:

PostgreSQL is only available inside the Docker network in production Compose.

Environment Variables

Backend:

  • SPRING_PROFILES_ACTIVE: local or prod
  • DB_URL: PostgreSQL JDBC URL
  • DB_USERNAME: database username
  • DB_PASSWORD: database password
  • FRONTEND_ORIGIN: allowed CORS origin, comma-separated if needed
  • SERVER_PORT: optional backend port, defaults to 8080
  • APP_JWT_SECRET: HMAC signing secret, at least 32 bytes
  • APP_JWT_ISSUER: JWT issuer, defaults to clinic-system
  • APP_JWT_ACCESS_TOKEN_MINUTES: access token lifetime in minutes
  • APP_ADMIN_EMAIL: bootstrap admin email
  • APP_ADMIN_PASSWORD: bootstrap admin password used only when the admin user does not already exist

Frontend:

  • VITE_API_BASE_URL: API base URL. Use http://localhost:8080/api locally and /api in production.

Local PostgreSQL:

  • POSTGRES_PASSWORD: optional, defaults to clinic_password in deploy/local/docker-compose.yml

API Overview

Public endpoints:

  • GET /api/health/ping
  • GET /actuator/health
  • GET /swagger-ui.html
  • GET /v3/api-docs
  • POST /api/auth/login

Authenticated endpoints:

  • GET /api/auth/me
  • GET /api/patients
  • POST /api/patients
  • GET /api/appointments
  • POST /api/appointments
  • GET /api/users
  • POST /api/users
  • GET /api/services
  • POST /api/services
  • GET /api/invoices
  • GET /api/invoices/{id}
  • POST /api/invoices
  • POST /api/invoices/{id}/items
  • POST /api/invoices/{id}/issue
  • POST /api/invoices/{id}/payments

Role access:

  • ADMIN: full application access, including user management
  • RECEPTIONIST: patients and appointments
  • DOCTOR: patients and appointments
  • BILLING: service catalogue, invoices, and payments

Swagger documents the API and includes a Bearer JWT security scheme. Use POST /api/auth/login, copy the returned token, and authorize in Swagger with Bearer <token>.

Database Migration

Flyway runs automatically when the Spring Boot application starts.

backend/src/main/resources/db/migration/V1__init.sql
backend/src/main/resources/db/migration/V2__billing.sql
backend/src/main/resources/db/migration/V3__auth.sql

The migrations create patients, appointments, service_items, invoices, invoice_items, payments, users, and roles tables with UUID primary keys, constraints, and useful indexes.

Testing

Backend tests:

cd backend
./mvnw test

Frontend checks:

cd frontend
npm ci
npm run lint
npm run build

Docker Compose config validation:

docker compose -f deploy/local/docker-compose.yml config
docker compose -f deploy/prod/docker-compose.yml --env-file deploy/prod/.env.example config

CI

GitHub Actions workflow: .github/workflows/ci.yml

It runs on push and pull_request:

  • Backend Maven tests on Java 21
  • Frontend npm ci, lint, and build on Node 22
  • Backend and frontend Docker image builds

Useful Commands

Build backend:

cd backend
./mvnw clean package

Build frontend:

cd frontend
npm ci
npm run build

Login and call a protected endpoint against production-style Compose:

TOKEN=$(curl -fsS -X POST http://localhost/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@clinic.local","password":"change_me_admin_password"}' \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["accessToken"])')

curl -H "Authorization: Bearer $TOKEN" http://localhost/api/auth/me
curl -H "Authorization: Bearer $TOKEN" http://localhost/api/patients

For local host-run backend commands, use http://localhost:8080 and the local admin password from backend/.env.

Stop local database:

docker compose -f deploy/local/docker-compose.yml down

Stop production-style stack:

docker compose -f deploy/prod/docker-compose.yml --env-file deploy/prod/.env down

Remove database volumes when you intentionally want a clean database:

docker compose -f deploy/local/docker-compose.yml down -v
docker compose -f deploy/prod/docker-compose.yml --env-file deploy/prod/.env down -v

Security Notes

  • Passwords are hashed with BCrypt.
  • JWT access tokens are signed with an HMAC secret configured by APP_JWT_SECRET.
  • Role-based authorization is enforced in the Spring Security configuration.
  • The React app stores the JWT in localStorage for portfolio/demo simplicity. This is acceptable for this project scope, but a high-security production application should consider stronger XSS controls, short token lifetimes, refresh-token rotation, or an HTTP-only cookie strategy.
  • Swagger and Actuator health are public in this demo so reviewers can inspect and verify the app quickly.
  • Demo credentials in .env.example files are intentionally non-secret examples and should be changed in persistent environments.

Portfolio Scope / Known Limitations

This project is intentionally scoped for job applications, not real clinic operations.

  • No real patient privacy, HIPAA, insurance, claims, or clinical compliance implementation.
  • No refresh tokens, password reset, MFA, or account lockout.
  • No frontend automated tests yet.
  • No Testcontainers integration tests yet.
  • No pagination/search on list endpoints yet.
  • No user edit/disable UI yet.
  • Screenshot files are placeholders until real screenshots are captured.

Troubleshooting

  • Backend cannot connect locally: confirm PostgreSQL is running with docker compose -f deploy/local/docker-compose.yml ps.
  • Password errors locally: use DB_PASSWORD=clinic_password or set POSTGRES_PASSWORD before creating the local database volume.
  • Port 5432 already in use: stop the other PostgreSQL service or change the local compose port mapping.
  • Frontend API calls fail locally: confirm the backend is on port 8080 and frontend/.env has VITE_API_BASE_URL=http://localhost:8080/api.
  • Login fails after changing APP_ADMIN_PASSWORD: the bootstrap password is only used when the admin user is first created. Reset the database volume or change the password directly in the database.
  • JWT startup errors: make sure APP_JWT_SECRET is at least 32 bytes.
  • Swagger UI is blank or unavailable: confirm the backend started successfully and Flyway migrations completed.
  • Production frontend loads but API fails: confirm the backend container is healthy enough to respond and that Nginx is proxying /api through the frontend container.
  • Duplicate smoke-test data returns 409 Conflict: use a different patient email or service name, or reset volumes with down -v.