Technical Architecture
This document provides detailed technical architecture, database schemas, APIs, and implementation details for all system modules.
System Architecture Overview
Three-Tier Application Architecture
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
├─────────────────────────────────────────────────────────────────┤
│ Hospital Portal (portal/) │ Console (console/) │
│ - React 18 + Vite │ - Next.js 15 │
│ - Ant Design v5 │ - Ant Design v5 │
│ - TanStack Router/Query │ - TanStack Query │
│ - Firebase Hosting │ - Firebase Hosting │
├─────────────────────────────────────────────────────────────────┤
│ Landing Website (landing/) │ Queue Display (queue/) │
│ - Next.js 15 │ - Next.js 15 │
│ - Tailwind CSS + shadcn/ui │ - Real-time SSE │
│ - Server Components │ - TV-optimized UI │
└─────────────────────────────────────────────────────────────────┘
↓ HTTPS/REST API
┌─────────────────────────────────────────────────────────────────┐
│ APPLICATION LAYER │
├─────────────────────────────────────────────────────────────────┤
│ NestJS Modular Monorepo (server/) │
│ ┌──────────┬──────────┬──────────┬──────────┬──────────┐ │
│ │ auth │ hms │ crm │ payment │ queue │ │
│ ├──────────┴──────────┴ ──────────┴──────────┴──────────┤ │
│ │ report │ │
│ └───────────────────────────────────────────────────────┘ │
│ Shared Libraries: prisma/ | common/ | redis/ │
│ Deployment: DigitalOcean App Platform │
└─────────────────────────────────────────────────────────────────┘
↓ Prisma ORM
┌─────────────────────────────────────────────────────────────────┐
│ DATA LAYER │
├─────────────────────────────────────────────────────────────── ──┤
│ PostgreSQL (Primary DB) │ Redis (Cache/Queue) │
│ - Multi-tenant data │ - Session management │
│ - Prisma migrations │ - Bull queue jobs │
│ - Full-text search │ - Real-time updates │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ IDENTITY LAYER │
├─────────────────────────────────────────────────────────────────┤
│ Keycloak (Identity Provider) │
│ - OAuth2 / OpenID Connect │ - User authentication │
│ - Multi-tenant realms │ - Role-based access │
│ - 2FA (Email OTP) │ - Session management │
│ - Impersonation │ - Audit logging │
└─────────────────────────────────────────────────────────────────┘
Backend Architecture (NestJS Monorepo)
Monorepo Structure
server/
├── apps/ # Modular applications
│ ├── auth/ # Authentication service (Keycloak integration)
│ ├── hms/ # Hospital Management System
│ ├── crm/ # Customer Relationship Management
│ ├── payment/ # Financial management
│ ├── queue/ # Queue & appointment system
│ ├── report/ # Report & Analytics Service
│ └── console/ # Console API (Platform Master)
│
├── libs/ # Shared libraries
│ ├── prisma/ # Database ORM
│ ├── common/ # Shared utilities
│ └── redis/ # Redis integration
│
├── prisma/
│ ├── schema.prisma # Database schema
│ ├── migrations/ # Migration history
│ └── seed.ts # Database seeding
│
└── nest-cli.json # NestJS monorepo config
Service Responsibilities
| Service | Purpose | Key Modules |
|---|---|---|
| auth | Authentication & Keycloak integration | Keycloak strategy, guards, 2FA |
| hms | Core hospital management | Patient, Employee, Lab, Pharmacy |
| crm | Customer relationship | Family membership, VIP, Visitors |
| payment | Financial operations | Invoice, Receipt, Reconciliation |
| queue | Scheduling | Appointments, Queue management |
| report | Analytics | Financial, Clinical, Operational reports |
| console | Platform admin | Hospital CRUD, Role templates |
API Design
RESTful API Structure
/api/{service}/{resource}/{action}
Examples:
GET /api/hms/patients # List patients
POST /api/hms/patients # Create patient
GET /api/hms/patients/:id # Get patient
PUT /api/hms/patients/:id # Update patient
DELETE /api/hms/patients/:id # Delete patient
GET /api/crm/families # List families
POST /api/crm/families/:id/members # Add family member
GET /api/payment/invoices # List invoices
POST /api/payment/invoices/:id/pay # Process payment
API Response Format
// Success Response
{
"success": true,
"data": { ... },
"meta": {
"page": 1,
"limit": 20,
"total": 100
}
}
// Error Response
{
"success": false,
"error": {
"code": "PATIENT_NOT_FOUND",
"message": "Patient with ID xxx not found",
"details": { ... }
}
}
Database Design
Multi-Tenancy Model
All tables include hospitalId for tenant isolation:
model Patient {
id String @id @default(uuid())
hospitalId String @map("hospital_id")
hospital Hospital @relation(fields: [hospitalId], references: [id])
firstName String @map("first_name")
lastName String @map("last_name")
phone String
email String?
dateOfBirth DateTime @map("date_of_birth")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("patients")
@@index([hospitalId])
@@index([phone])
}
Core Entities
| Entity | Description | Key Fields |
|---|---|---|
| Hospital | Tenant organization | id, name, prefix, theme |
| User | Staff accounts | id, hospitalId, email, role |
| Patient | Patient records | id, hospitalId, name, phone, dob |
| Family | Family membership | id, hospitalId, primaryContactId |
| Invoice | Billing records | id, hospitalId, patientId, total |
| Appointment | Scheduled visits | id, hospitalId, patientId, doctorId |
Keycloak Authentication
Multi-Tenant Realm Configuration
Each hospital has its own Keycloak realm:
Keycloak
├── master realm (Keycloak admin)
├── ihospita realm (Console users)
├── samaki realm (Hospital A users)
├── royal realm (Hospital B users)
└── ... (one realm per hospital)
Authentication Flow
JWT Token Structure
{
"exp": 1703862400,
"iat": 1703833600,
"iss": "https://auth.ihospita.com/realms/samaki",
"sub": "user-uuid",
"email": "doctor@samaki.com",
"realm_access": {
"roles": ["DOCTOR"]
},
"hospital_id": "hospital-uuid",
"hospital_prefix": "samaki"
}
Real-Time Features
Queue Updates (SSE/WebSocket)
// Server: Queue Service
@Sse('queue/:clinicId/stream')
queueStream(@Param('clinicId') clinicId: string) {
return this.queueService.getQueueStream(clinicId);
}
// Client: Queue Display
const eventSource = new EventSource('/api/queue/clinic-1/stream');
eventSource.onmessage = (event) => {
const queue = JSON.parse(event.data);
updateQueueDisplay(queue);
};
Redis Pub/Sub
// Publisher: When queue changes
await this.redis.publish(`queue:${clinicId}`, JSON.stringify(queue));
// Subscriber: Queue Service
this.redis.subscribe(`queue:${clinicId}`, (message) => {
this.broadcastToClients(clinicId, message);
});
File Storage
S3/Spaces Organization
ihospita-files/
├── samaki/ # Hospital prefix folder
│ ├── branding/ # Logo, favicon
│ ├── patients/ # Patient photos, documents
│ │ └── P-001/
│ │ ├── profile.jpg
│ │ └── id-card.pdf
│ ├── lab-reports/ # Lab result PDFs
│ ├── prescriptions/ # Prescription PDFs
│ └── invoices/ # Invoice PDFs
├── royal/ # Another hospital
└── console/ # Console admin files
Security Implementation
Request Flow with Security
Request → Cloudflare (WAF) → Kong → Service → Database
↓ ↓
DDoS Protection JWT Validation
SSL Termination Rate Limiting
IP Filtering RBAC Check
Guard Implementation
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles.includes(role));
}
}
// Usage
@Post('employees')
@Roles('ADMIN', 'OWNER')
async createEmployee(@Body() dto: CreateEmployeeDto) {
// Only ADMIN and OWNER can create employees
}
Error Handling
Global Exception Filter
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message = exception instanceof HttpException
? exception.message
: 'Internal server error';
response.status(status).json({
success: false,
error: {
code: this.getErrorCode(exception),
message,
timestamp: new Date().toISOString(),
},
});
}
}
Testing Strategy
Test Layers
| Layer | Tools | Coverage Target |
|---|---|---|
| Unit Tests | Jest | 80% |
| Integration Tests | Jest + Supertest | Critical paths |
| E2E Tests | Cypress | User flows |
Example Test
describe('PatientService', () => {
let service: PatientService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [PatientService, PrismaService],
}).compile();
service = module.get(PatientService);
});
it('should create patient with family', async () => {
const result = await service.create(hospitalId, {
firstName: 'John',
lastName: 'Doe',
phone: '+85512345678',
});
expect(result.patient).toBeDefined();
expect(result.family).toBeDefined();
});
});
Related Documentation
- System Design - High-level design
- Database Schema - Complete schema
- API Reference - All endpoints
- Infrastructure - DevOps details