Skip to main content
Version: v0.1.0

FHIR Integration

Complete guide to HL7 FHIR R4 integration in Ciyex EHR using HAPI FHIR.

Overview

Ciyex EHR includes a fully integrated HAPI FHIR server providing HL7 FHIR R4 compliant API for healthcare interoperability. This enables seamless data exchange with other healthcare systems, EHRs, and health information exchanges (HIEs).

Architecture

graph TB
subgraph "External Systems"
EHR[Other EHR Systems]
HIE[Health Information Exchange]
APP[Third-Party Apps]
end

subgraph "Ciyex EHR"
UI[Next.js UI]
API[Spring Boot API]
FHIR[HAPI FHIR Server]
end

subgraph "Data Layer"
DB[(PostgreSQL)]
end

EHR -->|FHIR R4| FHIR
HIE -->|FHIR R4| FHIR
APP -->|FHIR R4| FHIR
UI --> API
UI --> FHIR
API --> FHIR
FHIR --> DB
API --> DB

FHIR Server Details

  • Version: HAPI FHIR 8.2.1
  • FHIR Version: R4 (4.0.1)
  • Base URL: https://fhir.example.com/fhir
  • Capabilities: Full CRUD operations, search, transactions
  • Authentication: OAuth2/OIDC via Keycloak

Supported Resources

Patient Resources

ResourceSupportDescription
Patient✅ FullPatient demographics
Person✅ FullPerson information
RelatedPerson✅ FullRelated persons (family, caregivers)
Practitioner✅ FullHealthcare providers
PractitionerRole✅ FullProvider roles and specialties
Organization✅ FullHealthcare organizations
Location✅ FullPhysical locations

Clinical Resources

ResourceSupportDescription
Encounter✅ FullPatient visits
Condition✅ FullDiagnoses and problems
Observation✅ FullVitals, lab results
Procedure✅ FullProcedures performed
AllergyIntolerance✅ FullAllergies
MedicationRequest✅ FullPrescriptions
MedicationStatement✅ FullMedication history
Immunization✅ FullVaccinations
DiagnosticReport✅ FullLab and imaging reports
DocumentReference✅ FullClinical documents

Administrative Resources

ResourceSupportDescription
Appointment✅ FullAppointments
Schedule✅ FullProvider schedules
Slot✅ FullAvailable time slots
Coverage✅ FullInsurance coverage
Claim⚠️ PartialInsurance claims

Authentication

OAuth2 Token

POST https://aran-stg.zpoa.com/realms/master/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=ciyex-fhir
&client_secret=YOUR_CLIENT_SECRET
&scope=system/*.read system/*.write

Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 300,
"scope": "system/*.read system/*.write"
}

Using Token

GET /fhir/Patient/123
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

FHIR Operations

Create Resource

POST /fhir/Patient
Authorization: Bearer {token}
Content-Type: application/fhir+json

{
"resourceType": "Patient",
"identifier": [
{
"system": "http://example.com/mrn",
"value": "MRN-2024-00123"
}
],
"name": [
{
"use": "official",
"family": "Doe",
"given": ["John", "Michael"]
}
],
"gender": "male",
"birthDate": "1985-06-15",
"telecom": [
{
"system": "phone",
"value": "555-123-4567",
"use": "home"
},
{
"system": "email",
"value": "john.doe@example.com"
}
],
"address": [
{
"use": "home",
"line": ["123 Main St"],
"city": "Springfield",
"state": "IL",
"postalCode": "62701",
"country": "USA"
}
]
}

Response:

{
"resourceType": "Patient",
"id": "123",
"meta": {
"versionId": "1",
"lastUpdated": "2024-10-15T10:30:00Z"
},
"identifier": [...],
"name": [...],
"gender": "male",
"birthDate": "1985-06-15"
}

Read Resource

GET /fhir/Patient/123
Authorization: Bearer {token}
Accept: application/fhir+json

Update Resource

PUT /fhir/Patient/123
Authorization: Bearer {token}
Content-Type: application/fhir+json

{
"resourceType": "Patient",
"id": "123",
"identifier": [...],
"name": [
{
"use": "official",
"family": "Doe",
"given": ["John", "Michael", "Jr."]
}
],
"gender": "male",
"birthDate": "1985-06-15"
}

Delete Resource

DELETE /fhir/Patient/123
Authorization: Bearer {token}

Search Operations

GET /fhir/Patient?name=John&birthdate=1985-06-15
Authorization: Bearer {token}

Search Parameters

# By name
GET /fhir/Patient?name=Doe

# By identifier
GET /fhir/Patient?identifier=MRN-2024-00123

# By birthdate
GET /fhir/Patient?birthdate=1985-06-15

# By gender
GET /fhir/Patient?gender=male

# Combined
GET /fhir/Patient?name=John&birthdate=1985-06-15&gender=male
# By patient
GET /fhir/Observation?patient=Patient/123

# By code
GET /fhir/Observation?code=http://loinc.org|8867-4

# By date
GET /fhir/Observation?date=ge2024-01-01

# Combined
GET /fhir/Observation?patient=Patient/123&code=8867-4&date=ge2024-01-01

Search Modifiers

# Exact match
GET /fhir/Patient?name:exact=John

# Contains
GET /fhir/Patient?name:contains=oh

# Case-insensitive
GET /fhir/Patient?name:text=john

# Missing
GET /fhir/Patient?email:missing=false

Pagination

GET /fhir/Patient?_count=20&_offset=0

Include/Revinclude

# Include related resources
GET /fhir/Encounter?_include=Encounter:patient

# Reverse include
GET /fhir/Patient/123?_revinclude=Observation:patient

FHIR Resource Examples

Patient Resource

{
"resourceType": "Patient",
"id": "123",
"meta": {
"versionId": "1",
"lastUpdated": "2024-10-15T10:30:00Z",
"profile": ["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]
},
"identifier": [
{
"use": "official",
"system": "http://example.com/mrn",
"value": "MRN-2024-00123"
}
],
"active": true,
"name": [
{
"use": "official",
"family": "Doe",
"given": ["John", "Michael"]
}
],
"telecom": [
{
"system": "phone",
"value": "555-123-4567",
"use": "home"
},
{
"system": "email",
"value": "john.doe@example.com"
}
],
"gender": "male",
"birthDate": "1985-06-15",
"address": [
{
"use": "home",
"type": "both",
"line": ["123 Main St"],
"city": "Springfield",
"state": "IL",
"postalCode": "62701",
"country": "USA"
}
],
"maritalStatus": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus",
"code": "M",
"display": "Married"
}
]
},
"contact": [
{
"relationship": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0131",
"code": "C",
"display": "Emergency Contact"
}
]
}
],
"name": {
"family": "Doe",
"given": ["Jane"]
},
"telecom": [
{
"system": "phone",
"value": "555-987-6543"
}
]
}
]
}

Observation Resource (Vitals)

{
"resourceType": "Observation",
"id": "456",
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}
]
}
],
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "85354-9",
"display": "Blood pressure panel"
}
]
},
"subject": {
"reference": "Patient/123"
},
"effectiveDateTime": "2024-10-15T10:30:00Z",
"component": [
{
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8480-6",
"display": "Systolic blood pressure"
}
]
},
"valueQuantity": {
"value": 120,
"unit": "mmHg",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]"
}
},
{
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8462-4",
"display": "Diastolic blood pressure"
}
]
},
"valueQuantity": {
"value": 80,
"unit": "mmHg",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]"
}
}
]
}

Condition Resource

{
"resourceType": "Condition",
"id": "789",
"clinicalStatus": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/condition-clinical",
"code": "active"
}
]
},
"verificationStatus": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/condition-ver-status",
"code": "confirmed"
}
]
},
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/condition-category",
"code": "encounter-diagnosis",
"display": "Encounter Diagnosis"
}
]
}
],
"code": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "E11.9",
"display": "Type 2 diabetes mellitus without complications"
},
{
"system": "http://snomed.info/sct",
"code": "44054006",
"display": "Diabetes mellitus type 2"
}
]
},
"subject": {
"reference": "Patient/123"
},
"onsetDateTime": "2020-03-15",
"recordedDate": "2020-03-15T10:00:00Z"
}

Bulk Data Export

System-Level Export

GET /fhir/$export
Authorization: Bearer {token}
Prefer: respond-async

Patient-Level Export

GET /fhir/Patient/$export
Authorization: Bearer {token}
Prefer: respond-async

Group-Level Export

GET /fhir/Group/123/$export
Authorization: Bearer {token}
Prefer: respond-async

Response:

HTTP/1.1 202 Accepted
Content-Location: https://fhir.example.com/fhir/$export-poll-status?_jobId=abc123

Check Export Status

GET /fhir/$export-poll-status?_jobId=abc123
Authorization: Bearer {token}

Multi-Tenancy

HAPI FHIR server supports multi-tenancy using partitions:

Tenant Header

GET /fhir/Patient/123
Authorization: Bearer {token}
X-Tenant-ID: practice_1

Partition Configuration

@Bean
public PartitionSettings partitionSettings() {
PartitionSettings settings = new PartitionSettings();
settings.setPartitioningEnabled(true);
settings.setUnnamedPartitionMode(false);
return settings;
}

SMART on FHIR

Ciyex EHR supports SMART on FHIR for third-party app integration:

Authorization Endpoint

https://aran-stg.zpoa.com/realms/master/protocol/openid-connect/auth

Token Endpoint

https://aran-stg.zpoa.com/realms/master/protocol/openid-connect/token

Scopes

  • patient/*.read - Read patient data
  • patient/*.write - Write patient data
  • user/*.read - Read user data
  • user/*.write - Write user data
  • launch - Launch context
  • launch/patient - Patient launch context

Performance Optimization

Indexing

HAPI FHIR automatically creates indexes for search parameters:

-- Example indexes
CREATE INDEX idx_patient_family ON patient_name (family_name);
CREATE INDEX idx_patient_birthdate ON patient (birth_date);
CREATE INDEX idx_observation_patient ON observation (patient_id);

Caching

Enable caching for better performance:

spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=1000,expireAfterWrite=10m

Troubleshooting

Resource Not Found

Error:

{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "not-found",
"diagnostics": "Resource Patient/999 not found"
}
]
}

Solution: Verify resource ID exists

Invalid Resource

Error:

{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "invalid",
"diagnostics": "Invalid resource: birthDate is required"
}
]
}

Solution: Validate resource against FHIR schema

Authentication Failed

Error:

{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "security",
"diagnostics": "Invalid or expired token"
}
]
}

Solution: Refresh OAuth2 token

Next Steps