Xem bản video
1. FHIR Client Overview
FHIR Client là thư viện giúp ứng dụng giao tiếp với FHIR Server qua RESTful API. Mỗi ngôn ngữ có thư viện riêng.
| Ngôn ngữ | Thư viện | Đặc điểm |
|---|---|---|
| Java | HAPI FHIR Client | Đầy đủ nhất, Generic + Fluent API |
| JavaScript | fhirclient.js / fhir.js | Browser + Node.js, SMART on FHIR |
| Python | fhirclient | SMART on FHIR, Pydantic models |
| .NET | Firely SDK (Hl7.Fhir) | NuGet package, strong typing |
| Swift | Swift-FHIR | iOS/macOS native |
2. HAPI FHIR Client (Java)
Generic Client
FhirContext ctx = FhirContext.forR5();
IGenericClient client = ctx.newRestfulGenericClient(
"http://localhost:8080/fhir");
// READ
Patient patient = client.read()
.resource(Patient.class)
.withId("patient-001")
.execute();
System.out.println("Bệnh nhân: " +
patient.getNameFirstRep().getNameAsSingleString());
// CREATE
Patient newPatient = new Patient();
newPatient.addName()
.setFamily("Nguyễn")
.addGiven("Văn A");
newPatient.addIdentifier()
.setSystem("http://xdev.asia/fhir/sid/cccd")
.setValue("001234567890");
newPatient.setGender(Enumerations.AdministrativeGender.MALE);
newPatient.setBirthDateElement(new DateType("1990-05-15"));
MethodOutcome outcome = client.create()
.resource(newPatient)
.execute();
System.out.println("Created ID: " +
outcome.getId().getIdPart());
// SEARCH
Bundle results = client.search()
.forResource(Patient.class)
.where(Patient.NAME.matches().value("Nguyễn"))
.and(Patient.BIRTHDATE.afterOrEquals().day("1990-01-01"))
.returnBundle(Bundle.class)
.execute();
for (Bundle.BundleEntryComponent entry : results.getEntry()) {
Patient p = (Patient) entry.getResource();
System.out.println(p.getNameFirstRep().getNameAsSingleString());
}
Fluent Client — Annotation-based
// Định nghĩa interface
@FhirClient
public interface PatientClient {
@Read
Patient readPatient(@IdParam IdType id);
@Search
Bundle searchByName(
@RequiredParam(name = Patient.SP_NAME) StringParam name);
@Create
MethodOutcome createPatient(@ResourceParam Patient patient);
@Update
MethodOutcome updatePatient(
@IdParam IdType id,
@ResourceParam Patient patient);
@Delete
void deletePatient(@IdParam IdType id);
}
// Sử dụng
PatientClient patientClient = ctx.newRestfulClient(
PatientClient.class, "http://localhost:8080/fhir");
Patient patient = patientClient.readPatient(new IdType("patient-001"));
3. JavaScript FHIR Client (fhirclient.js)
npm install fhirclient
import FHIR from 'fhirclient';
// Kết nối FHIR Server (standalone launch)
const client = FHIR.client({
serverUrl: 'http://localhost:8080/fhir'
});
// Read Patient
const patient = await client.request('Patient/patient-001');
console.log(`Bệnh nhân: ${patient.name[0].family} ${patient.name[0].given[0]}`);
// Search
const bundle = await client.request(
'Patient?name=Nguyen&birthdate=ge1990-01-01&_count=20'
);
bundle.entry?.forEach(entry => {
console.log(entry.resource.name[0].text);
});
// Create
const newPatient = {
resourceType: 'Patient',
name: [{ family: 'Trần', given: ['Thị B'] }],
gender: 'female',
birthDate: '1995-08-20',
identifier: [{
system: 'http://xdev.asia/fhir/sid/cccd',
value: '001987654321'
}]
};
const created = await client.create(newPatient);
console.log('Created:', created.id);
SMART on FHIR Launch
// launch.html — redirect page
FHIR.oauth2.authorize({
clientId: 'my-fhir-app',
scope: 'patient/*.read user/Patient.read launch/patient',
redirectUri: '/callback'
});
// callback.html — sau khi authorize
FHIR.oauth2.ready()
.then(client => {
// Tự động lấy Patient context
return client.patient.read();
})
.then(patient => {
console.log('Current patient:', patient.name[0].text);
// Lấy Observations của patient hiện tại
return client.request(
`Observation?patient=${patient.id}&code=8867-4&_sort=-date&_count=10`
);
})
.then(bundle => {
bundle.entry?.forEach(entry => {
const obs = entry.resource;
console.log(`${obs.code.text}: ${obs.valueQuantity.value} ${obs.valueQuantity.unit}`);
});
});
4. Python FHIR Client
pip install fhirclient
from fhirclient import client
from fhirclient.models.patient import Patient
from fhirclient.models.observation import Observation
# Cấu hình
settings = {
'app_id': 'my_fhir_app',
'api_base': 'http://localhost:8080/fhir'
}
smart = client.FHIRClient(settings=settings)
# Read Patient
patient = Patient.read('patient-001', smart.server)
print(f"Bệnh nhân: {patient.name[0].family} {patient.name[0].given[0]}")
# Search
search = Patient.where(struct={
'name': 'Nguyen',
'birthdate': 'ge1990-01-01'
})
patients = search.perform_resources(smart.server)
for p in patients:
print(f" - {p.name[0].text} ({p.birthDate.isostring})")
# Create Observation
obs = Observation()
obs.status = 'final'
obs.code = {
'coding': [{
'system': 'http://loinc.org',
'code': '8867-4',
'display': 'Heart rate'
}]
}
obs.subject = {'reference': f'Patient/patient-001'}
obs.valueQuantity = {
'value': 72,
'unit': 'beats/minute',
'system': 'http://unitsofmeasure.org',
'code': '/min'
}
result = obs.create(smart.server)
print(f"Created Observation: {result}")
5. Xây dựng ứng dụng Quản lý BN với React
// PatientList.tsx
import { useState, useEffect } from 'react';
import FHIR from 'fhirclient';
interface FHIRPatient {
id: string;
name: Array<{ family: string; given: string[] }>;
gender: string;
birthDate: string;
}
export function PatientList() {
const [patients, setPatients] = useState<FHIRPatient[]>([]);
const [search, setSearch] = useState('');
const client = FHIR.client({ serverUrl: '/fhir' });
useEffect(() => {
loadPatients();
}, [search]);
async function loadPatients() {
const params = search
? `Patient?name=${encodeURIComponent(search)}&_count=20`
: 'Patient?_count=20&_sort=-_lastUpdated';
const bundle = await client.request(params);
const list = bundle.entry?.map(
(e: any) => e.resource as FHIRPatient
) || [];
setPatients(list);
}
return (
<div>
<input
placeholder="Tìm bệnh nhân..."
value={search}
onChange={e => setSearch(e.target.value)}
/>
<table>
<thead>
<tr>
<th>ID</th>
<th>Họ tên</th>
<th>Giới tính</th>
<th>Ngày sinh</th>
</tr>
</thead>
<tbody>
{patients.map(p => (
<tr key={p.id}>
<td>{p.id}</td>
<td>{p.name[0]?.family} {p.name[0]?.given?.join(' ')}</td>
<td>{p.gender}</td>
<td>{p.birthDate}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
6. Error Handling và Retry Patterns
// Java - Retry với exponential backoff
public <T extends IBaseResource> T readWithRetry(
Class<T> resourceClass, String id, int maxRetries) {
int attempt = 0;
while (attempt < maxRetries) {
try {
return client.read()
.resource(resourceClass)
.withId(id)
.execute();
} catch (ResourceNotFoundException e) {
throw e; // 404 — không retry
} catch (FhirClientConnectionException e) {
attempt++;
if (attempt == maxRetries) throw e;
long delay = (long) Math.pow(2, attempt) * 1000;
Thread.sleep(delay);
}
}
throw new IllegalStateException("Max retries exceeded");
}
// OperationOutcome handling
try {
client.create().resource(patient).execute();
} catch (UnprocessableEntityException e) {
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
for (var issue : oo.getIssue()) {
System.err.println(String.format("[%s] %s: %s",
issue.getSeverity(),
issue.getLocation(),
issue.getDiagnostics()));
}
}
7. Tổng kết
HAPI FHIR Client (Java) — Generic Client cho flexibility, Fluent Client cho type-safety
fhirclient.js — SMART on FHIR launch, browser + Node.js
Python fhirclient — Pydantic models, dễ tích hợp data pipeline
React + FHIR — SPA quản lý bệnh nhân kết nối trực tiếp FHIR Server
Error handling — OperationOutcome parsing, retry với exponential backoff