A production-ready healthcare management REST API built with Spring Boot 3, providing comprehensive endpoints to manage patients, doctors, and appointments. Features JWT authentication, PostgreSQL integration, Flyway migrations, and Docker containerization for seamless deployment.
Java Spring Boot PostgreSQL Docker License
- ๐ฅ Complete CRUD Operations: Manage patients, doctors, and appointments
- ๐ JWT Authentication: Secure endpoints with token-based auth
- ๐๏ธ PostgreSQL Integration: Robust relational database with JPA
- ๐ Database Migrations: Version-controlled schema with Flyway
- ๐ Search & Pagination: Efficient data retrieval with filtering
- ๐๏ธ Soft Delete: Preserve data integrity with logical deletion
- โ Validation: Comprehensive input validation with Bean Validation
- ๐ณ Docker Support: Containerized deployment with Docker Compose
- ๐ API Documentation: Interactive Swagger/OpenAPI documentation
- ๐งช Test Coverage: Unit and integration tests with Testcontainers
- ๐ Production Ready: Error handling, logging, and monitoring
- ๐ Audit Trail: Automatic timestamps for all entities
- Architecture
- Tech Stack
- Prerequisites
- Installation
- Configuration
- Running the Application
- API Documentation
- Database Schema
- Authentication
- API Endpoints
- Testing
- Deployment
- Troubleshooting
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโ
โ Client โโโโโโโถโ Controller โโโโโโโถโ Service โโโโโโโถโ Repo โ
โ (Swagger/ โ โ (REST API) โ โ (Business โ โ (JPA) โ
โ Postman) โโโโโโโโ โโโโโโโโ Logic) โโโโโโโโ โ
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโ
โ Security โ โPostgreSQLโ
โ (JWT Filter)โ โ Database โ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโ
- Controller Layer: REST endpoints, request/response handling
- Service Layer: Business logic, transaction management
- Repository Layer: Data access with Spring Data JPA
- Entity Layer: JPA entities with relationships
- DTO Layer: Data transfer objects for API contracts
- Security Layer: JWT authentication and authorization
| Technology | Purpose | Version |
|---|---|---|
| Java | Programming Language | 17+ |
| Spring Boot | Application Framework | 3.x |
| Spring Data JPA | ORM & Data Access | 3.x |
| Spring Security | Authentication & Authorization | 6.x |
| PostgreSQL | Relational Database | 15 |
| Flyway | Database Migration | Latest |
| Hibernate | ORM Implementation | 6.x |
| JWT | Token-based Authentication | Latest |
| Lombok | Boilerplate Reduction | Latest |
| MapStruct | Object Mapping | Latest |
| Swagger/OpenAPI | API Documentation | 3.x |
| JUnit 5 | Testing Framework | 5.x |
| Testcontainers | Integration Testing | Latest |
| Docker | Containerization | Latest |
| Maven | Build Tool | 3.9+ |
Before you begin, ensure you have:
- Java Development Kit (JDK) 17 or higher
- Maven 3.9+ for dependency management
- Docker and Docker Compose for containerization
- PostgreSQL 15 (optional if using Docker)
- Postman or cURL for API testing
- Your favorite IDE (IntelliJ IDEA recommended)
git clone https://github.com/Devatva24/Patient-Management.git
cd Patient-ManagementCreate a .env file in the root directory:
# Database Configuration POSTGRES_DB=pms POSTGRES_USER=pms POSTGRES_PASSWORD=your_secure_password # Spring Configuration SPRING_PROFILES_ACTIVE=dev SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/pms SPRING_DATASOURCE_USERNAME=pms SPRING_DATASOURCE_PASSWORD=your_secure_password # JWT Configuration JWT_SECRET=your-256-bit-secret-key-change-this-in-production JWT_EXPIRATION=86400000 # Server Configuration SERVER_PORT=8080
mvn clean install
Update src/main/resources/application.yml:
server: port: ${SERVER_PORT:8080} spring: application: name: patient-management-system datasource: url: ${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/pms} username: ${SPRING_DATASOURCE_USERNAME:pms} password: ${SPRING_DATASOURCE_PASSWORD:pms} driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: validate show-sql: false properties: hibernate: format_sql: true dialect: org.hibernate.dialect.PostgreSQLDialect jdbc.lob.non_contextual_creation: true flyway: enabled: true baseline-on-migrate: true locations: classpath:db/migration security: jwt: secret: ${JWT_SECRET:change-me-please} expiration: ${JWT_EXPIRATION:86400000} logging: level: root: INFO com.example.pms: DEBUG org.springframework.web: INFO org.hibernate.SQL: DEBUG
# Create database sudo -u postgres psql CREATE DATABASE pms; CREATE USER pms WITH PASSWORD 'your_password'; GRANT ALL PRIVILEGES ON DATABASE pms TO pms; \q
mvn spring-boot:run
# Build and start services docker-compose up --build # Or run in detached mode docker-compose up -d # View logs docker-compose logs -f # Stop services docker-compose down
The application will be available at:
- API: http://localhost:8080
- Swagger UI: http://localhost:8080/swagger-ui/index.html
- API Docs: http://localhost:8080/v3/api-docs
# Build JAR mvn clean package -DskipTests # Run JAR java -jar target/patient-management-0.0.1-SNAPSHOT.jar
Access interactive API documentation at:
http://localhost:8080/swagger-ui/index.html
Access raw OpenAPI JSON at:
http://localhost:8080/v3/api-docs
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Doctor โ โ Appointment โ โ Patient โ
โโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโค
โ id (PK) โโโโโโ โ id (PK) โ โโโโโถโ id (PK) โ
โ first_name โ โ โ patient_id (FK) โโโโโโ โ first_name โ
โ last_name โ โโโโโโ doctor_id (FK) โ โ last_name โ
โ email โ โ start_time โ โ email โ
โ specialization โ โ end_time โ โ phone โ
โ created_at โ โ notes โ โ dob โ
โ updated_at โ โ status โ โ gender โ
โโโโโโโโโโโโโโโโโโโ โ created_at โ โ created_at โ
โ updated_at โ โ updated_at โ
โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
doctors
CREATE TABLE doctors ( id BIGSERIAL PRIMARY KEY, first_name VARCHAR(80) NOT NULL, last_name VARCHAR(80) NOT NULL, email VARCHAR(120) UNIQUE NOT NULL, specialization VARCHAR(120), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
patients
CREATE TABLE patients ( id BIGSERIAL PRIMARY KEY, first_name VARCHAR(80) NOT NULL, last_name VARCHAR(80) NOT NULL, email VARCHAR(120) UNIQUE, phone VARCHAR(30), dob DATE, gender VARCHAR(10), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
appointments
CREATE TABLE appointments ( id BIGSERIAL PRIMARY KEY, patient_id BIGINT NOT NULL REFERENCES patients(id), doctor_id BIGINT NOT NULL REFERENCES doctors(id), start_time TIMESTAMP NOT NULL, end_time TIMESTAMP NOT NULL, notes TEXT, status VARCHAR(20) DEFAULT 'SCHEDULED', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_patient FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE CASCADE, CONSTRAINT fk_doctor FOREIGN KEY (doctor_id) REFERENCES doctors(id) ON DELETE CASCADE ); CREATE INDEX idx_appt_patient ON appointments(patient_id); CREATE INDEX idx_appt_doctor ON appointments(doctor_id); CREATE INDEX idx_appt_start ON appointments(start_time); CREATE INDEX idx_appt_status ON appointments(status);
1. User Login โ POST /api/auth/login
2. Server validates credentials
3. Server generates JWT token
4. Client stores token
5. Client includes token in Authorization header
6. Server validates token for protected endpoints
curl -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{ "username": "admin", "password": "admin123" }'
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"type": "Bearer",
"expiresIn": 86400000
}Include the token in the Authorization header:
curl -X GET http://localhost:8080/api/v1/patients \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."http://localhost:8080/api/v1
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/login |
Login and get JWT token |
| POST | /api/auth/register |
Register new user |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /patients |
List all patients (paginated) | โ |
| GET | /patients/{id} |
Get patient by ID | โ |
| POST | /patients |
Create new patient | โ |
| PUT | /patients/{id} |
Update patient | โ |
| PATCH | /patients/{id} |
Partial update | โ |
| DELETE | /patients/{id} |
Delete patient (soft) | โ |
| GET | /patients/search?q={query} |
Search patients | โ |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /doctors |
List all doctors | โ |
| GET | /doctors/{id} |
Get doctor by ID | โ |
| POST | /doctors |
Create new doctor | โ |
| PUT | /doctors/{id} |
Update doctor | โ |
| DELETE | /doctors/{id} |
Delete doctor | โ |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /appointments |
List all appointments | โ |
| GET | /appointments/{id} |
Get appointment by ID | โ |
| POST | /appointments |
Create appointment | โ |
| PUT | /appointments/{id} |
Update appointment | โ |
| DELETE | /appointments/{id} |
Cancel appointment | โ |
| GET | /appointments/patient/{id} |
Get patient appointments | โ |
| GET | /appointments/doctor/{id} |
Get doctor appointments | โ |
curl -X POST http://localhost:8080/api/v1/patients \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{ "firstName": "Rahul", "lastName": "Sharma", "email": "rahul.sharma@example.com", "phone": "+91-9876543210", "dob": "1990-05-15", "gender": "MALE" }'
Response (201 Created):
{
"id": 1,
"firstName": "Rahul",
"lastName": "Sharma",
"email": "rahul.sharma@example.com",
"phone": "+91-9876543210",
"dob": "1990ๅนด05ๆ15ๆฅ",
"gender": "MALE",
"createdAt": "2026ๅนด01ๆ14ๆฅT10:30:00",
"updatedAt": "2026ๅนด01ๆ14ๆฅT10:30:00"
}curl -X POST http://localhost:8080/api/v1/doctors \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{ "firstName": "Dr. Priya", "lastName": "Patel", "email": "priya.patel@hospital.com", "specialization": "Cardiology" }'
curl -X POST http://localhost:8080/api/v1/appointments \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{ "patientId": 1, "doctorId": 1, "startTime": "2026-01-20T10:00:00", "endTime": "2026-01-20T10:30:00", "notes": "Regular checkup", "status": "SCHEDULED" }'
curl -X GET "http://localhost:8080/api/v1/patients?page=0&size=10&sort=lastName,asc" \ -H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"content": [...],
"totalElements": 50,
"totalPages": 5,
"size": 10,
"number": 0
}curl -X GET "http://localhost:8080/api/v1/patients/search?q=sharma" \ -H "Authorization: Bearer YOUR_TOKEN"
mvn testmvn test -Dtest=PatientServiceTestmvn verify
mvn clean test jacoco:reportView coverage report at: target/site/jacoco/index.html
src/test/java/
โโโ unit/
โ โโโ service/
โ โ โโโ PatientServiceTest.java
โ โ โโโ DoctorServiceTest.java
โ โ โโโ AppointmentServiceTest.java
โ โโโ mapper/
โ โโโ PatientMapperTest.java
โโโ integration/
โโโ controller/
โ โโโ PatientControllerTest.java
โ โโโ DoctorControllerTest.java
โ โโโ AppointmentControllerTest.java
โโโ repository/
โโโ PatientRepositoryTest.java
@ExtendWith(MockitoExtension.class) class PatientServiceTest { @Mock private PatientRepository patientRepository; @InjectMocks private PatientService patientService; @Test void shouldCreatePatient() { // given PatientDTO patientDTO = new PatientDTO(); patientDTO.setFirstName("John"); // when PatientDTO result = patientService.createPatient(patientDTO); // then assertNotNull(result); assertEquals("John", result.getFirstName()); } }
# Build stage FROM maven:3.9-eclipse-temurin-17 AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn clean package -DskipTests # Runtime stage FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/app.jar"]
version: '3.8' services: postgres: image: postgres:15-alpine container_name: pms-postgres environment: POSTGRES_DB: ${POSTGRES_DB:-pms} POSTGRES_USER: ${POSTGRES_USER:-pms} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-pms} ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U pms"] interval: 10s timeout: 5s retries: 5 api: build: . container_name: pms-api depends_on: postgres: condition: service_healthy environment: SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod} SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB:-pms} SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-pms} SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD:-pms} JWT_SECRET: ${JWT_SECRET:-change-me-please} ports: - "8080:8080" restart: unless-stopped volumes: postgres_data:
# Build image docker build -t patient-management:latest . # Run with Docker Compose docker-compose up -d # View logs docker-compose logs -f api # Stop services docker-compose down # Clean up volumes docker-compose down -v
- Launch EC2 Instance (Ubuntu 22.04)
- Install Docker and Docker Compose
- Clone repository and configure environment
- Run with Docker Compose
- Configure Nginx as reverse proxy
- Setup SSL with Let's Encrypt
# Login to Heroku heroku login # Create app heroku create patient-management-api # Add PostgreSQL addon heroku addons:create heroku-postgresql:mini # Set environment variables heroku config:set JWT_SECRET=your-secret-key # Deploy git push heroku master # Open app heroku open
# Build and push to Container Registry gcloud builds submit --tag gcr.io/PROJECT_ID/patient-management # Deploy to Cloud Run gcloud run deploy patient-management \ --image gcr.io/PROJECT_ID/patient-management \ --platform managed \ --region us-central1 \ --allow-unauthenticated
Solution:
# Find process using port 8080 lsof -i :8080 # Kill the process kill -9 <PID> # Or change port in application.yml server.port=8081
Solution:
# Check PostgreSQL is running sudo systemctl status postgresql # Verify credentials in application.yml # Check if database exists psql -U pms -d pms -h localhost
Solution:
# Check migration files in src/main/resources/db/migration # Ensure files are named correctly: V1__init.sql # Repair Flyway mvn flyway:repair # Or baseline mvn flyway:baseline
Solution:
- Check JWT_SECRET is properly set
- Verify token hasn't expired
- Ensure correct Authorization header format:
Bearer <token>
Solution:
# Clean Docker cache docker system prune -a # Rebuild without cache docker-compose build --no-cache
Access health and metrics endpoints:
# Health check curl http://localhost:8080/actuator/health # Metrics curl http://localhost:8080/actuator/metrics # Info curl http://localhost:8080/actuator/info
Configure logging in application.yml:
logging: file: name: logs/patient-management.log pattern: console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n" file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" level: root: INFO com.example.pms: DEBUG
Contributions are welcome! Follow these steps:
- Fork the repository
- Create a feature branch
git checkout -b feature/amazing-feature
- Make your changes
- Write tests
- Commit your changes
git commit -m 'Add amazing feature' - Push to the branch
git push origin feature/amazing-feature
- Open a Pull Request
- Follow Java naming conventions
- Write meaningful commit messages
- Add JavaDoc for public methods
- Maintain test coverage above 80%
- Use Lombok to reduce boilerplate
- Follow REST API best practices
This project is licensed under the MIT License - see the LICENSE file for details.
- Spring Boot - Application framework
- PostgreSQL - Database system
- Flyway - Database migration tool
- Swagger - API documentation
- Testcontainers - Testing framework
Devatva Rachit
- GitHub: @Devatva24
- Project: Patient Management System
For issues and questions:
- Open an issue on GitHub
- Check existing issues and discussions
- Review the troubleshooting section
Give a โญ๏ธ if this project helped you!
- Add email notifications for appointments
- Implement medical records management
- Add prescription module
- Integrate payment processing
- Add SMS reminders
- Implement video consultation
- Add analytics dashboard
- Multi-language support
- Mobile app integration
- Export reports to PDF
Built with โค๏ธ for Healthcare by Devatva Rachit