TL;DR
HL7 FHIR (Fast Healthcare Interoperability Resources) ist der moderne Standard für den Austausch von Gesundheitsdaten, der RESTful APIs mit JSON/XML-Antworten nutzt. Es bietet standardisierte Ressourcen für Patienten, Beobachtungen, Medikamente und mehr, mit OAuth 2.0-Authentifizierung und SMART on FHIR für die App-Integration. Dieser Leitfaden behandelt die FHIR-Architektur, Ressourcentypen, Suchparameter, Authentifizierung und Implementierungsstrategien für die Produktion.
Einführung
Die Fragmentierung von Gesundheitsdaten kostet das US-Gesundheitssystem jährlich 30 Milliarden Dollar. Für Entwickler, die Gesundheitsanwendungen erstellen, ist die Integration der HL7 FHIR API keine Option, sondern der Industriestandard, der von CMS vorgeschrieben und von Epic, Cerner sowie allen großen EHR-Anbietern übernommen wird.
Hier ist die Realität: Anbieter, die FHIR-fähige Apps nutzen, reduzieren die Zeit für die Versorgungskoordination um 40 % und eliminieren 85 % der faxbasierten Anfragen nach Akten. Eine solide FHIR API-Integration ermöglicht einen nahtlosen Datenaustausch über EHRs, Patientenportale und Plattformen zur Versorgungskoordination hinweg.
Dieser Leitfaden führt Sie durch den gesamten Integrationsprozess der HL7 FHIR API. Sie lernen die FHIR-Architektur, Ressourcentypen, Suchparameter, OAuth 2.0-Authentifizierung, SMART on FHIR-Integration und Bereitstellungsstrategien für die Produktion kennen. Am Ende verfügen Sie über eine produktionsreife FHIR-Integration.
Was ist HL7 FHIR?
FHIR (Fast Healthcare Interoperability Resources) ist ein Standard-Framework für den elektronischen Austausch von Gesundheitsinformationen. Entwickelt von Health Level Seven International (HL7), verwendet FHIR moderne Webtechnologien wie RESTful APIs, JSON, XML und OAuth 2.0.

FHIR-Ressourcentypen
FHIR definiert über 140 Ressourcentypen. Zu den Kernressourcen gehören:
| Ressource | Zweck | Häufige Anwendungsfälle |
|---|---|---|
| Patient | Demografische Daten | Patientensuche, Registrierung |
| Practitioner | Anbieterinformationen | Verzeichnis, Terminplanung |
| Encounter | Besuche/Aufnahmen | Behandlungsepisoden, Abrechnung |
| Observation | Klinische Daten | Vitalwerte, Laborergebnisse, Beurteilungen |
| Condition | Probleme/Diagnosen | Problemlisten, Behandlungsplanung |
| MedicationRequest | Verschreibungen | E-Rezept, Medikationshistorie |
| AllergyIntolerance | Allergien | Sicherheitsprüfungen, Warnungen |
| Immunization | Impfungen | Impfprotokolle |
| DiagnosticReport | Labor-/Bildgebungsberichte | Ergebnisübermittlung |
| DocumentReference | Klinische Dokumente | CCD, Entlassungszusammenfassungen |
FHIR API-Architektur
FHIR verwendet eine RESTful API-Struktur:
https://fhir-server.com/fhir/{resourceType}/{id}
FHIR-Versionen im Vergleich
| Version | Status | Anwendungsfall |
|---|---|---|
| R4 (4.0.1) | Aktueller STU | Produktionsimplementierungen |
| R4B (4.3) | Testimplementierung | Frühe Anwender |
| R5 (5.0.0) | Entwurf STU | Zukünftige Implementierungen |
| DSTU2 | Veraltet | Nur für Legacy-Systeme |
CMS verlangt von zertifizierten EHRs die Unterstützung von FHIR R4 für Patient Access und Provider Access APIs.
Erste Schritte: FHIR-Server-Zugang
Schritt 1: Wählen Sie Ihren FHIR-Server
Optionen für die Bereitstellung eines FHIR-Servers:
| Server | Typ | Kosten | Am besten für |
|---|---|---|---|
| Azure API for FHIR | Verwaltet | Pay-per-Use | Unternehmen, Azure-Nutzer |
| AWS HealthLake | Verwaltet | Pay-per-Use | AWS-Umgebungen |
| Google Cloud Healthcare API | Verwaltet | Pay-per-Use | GCP-Umgebungen |
| HAPI FHIR | Open Source | Selbst gehostet | Benutzerdefinierte Bereitstellungen |
| Epic FHIR Server | Kommerziell | Epic-Kunden | Epic EHR-Integration |
| Cerner Ignite FHIR | Kommerziell | Cerner-Kunden | Cerner EHR-Integration |
Schritt 2: Server-Anmeldeinformationen erhalten
Für Cloud-FHIR-Dienste:
# Azure API für FHIR
# 1. FHIR-Dienst im Azure-Portal erstellen
# 2. Authentifizierung konfigurieren (OAuth 2.0 oder AAD)
# 3. FHIR-Endpunkt abrufen: https://{service-name}.azurehealthcareapis.com
# 4. Client-App für OAuth registrieren
# AWS HealthLake
# 1. Datenspeicher in der AWS-Konsole erstellen
# 2. IAM-Rollen konfigurieren
# 3. Endpunkt abrufen: https://healthlake.{region}.amazonaws.com
Schritt 3: FHIR RESTful-Operationen verstehen
FHIR unterstützt Standard-HTTP-Methoden:
| Operation | HTTP-Methode | Endpunkt | Beschreibung |
|---|---|---|---|
| Lesen | GET | /{resourceType}/{id} |
Spezifische Ressource abrufen |
| Suchen | GET | /{resourceType}?param=value |
Ressourcen suchen |
| Erstellen | POST | /{resourceType} |
Neue Ressource erstellen |
| Aktualisieren | PUT | /{resourceType}/{id} |
Ressource ersetzen |
| Patchen | PATCH | /{resourceType}/{id} |
Partielle Aktualisierung |
| Löschen | DELETE | /{resourceType}/{id} |
Ressource entfernen |
| Historie | GET | /{resourceType}/{id}/_history |
Ressourcenversionen |
Schritt 4: Ihren ersten FHIR-Aufruf tätigen
Konnektivität testen:
curl -X GET "https://fhir-server.com/fhir/metadata" \
-H "Accept: application/fhir+json" \
-H "Authorization: Bearer {token}"
Erwartete Antwort:
{
"resourceType": "CapabilityStatement",
"status": "active",
"date": "2026-03-25",
"fhirVersion": "4.0.1",
"rest": [{
"mode": "server",
"resource": [
{ "type": "Patient" },
{ "type": "Observation" },
{ "type": "Condition" }
]
}]
}
FHIR-Kernoperationen
Eine Patientenressource lesen
Patient nach ID abrufen:
const FHIR_BASE_URL = process.env.FHIR_BASE_URL;
const FHIR_TOKEN = process.env.FHIR_TOKEN;
const fhirRequest = async (endpoint, options = {}) => {
const response = await fetch(`${FHIR_BASE_URL}/fhir${endpoint}`, {
...options,
headers: {
'Accept': 'application/fhir+json',
'Authorization': `Bearer ${FHIR_TOKEN}`,
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(`FHIR-Fehler: ${error.issue?.[0]?.details?.text || response.statusText}`);
}
return response.json();
};
// Patient nach ID lesen
const getPatient = async (patientId) => {
const patient = await fhirRequest(`/Patient/${patientId}`);
return patient;
};
// Verwendung
const patient = await getPatient('12345');
console.log(`Patient: ${patient.name[0].given[0]} ${patient.name[0].family}`);
console.log(`Geburtsdatum: ${patient.birthDate}`);
console.log(`Geschlecht: ${patient.gender}`);
Struktur der Patientenressource
{
"resourceType": "Patient",
"id": "12345",
"identifier": [
{
"use": "usual",
"type": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "MR"
}]
},
"system": "http://hospital.example.org",
"value": "MRN123456"
}
],
"name": [
{
"use": "official",
"family": "Smith",
"given": ["John", "Michael"]
}
],
"telecom": [
{
"system": "phone",
"value": "555-123-4567",
"use": "home"
},
{
"system": "email",
"value": "john.smith@email.com"
}
],
"gender": "male",
"birthDate": "1985-06-15",
"address": [
{
"use": "home",
"line": ["123 Main Street"],
"city": "Springfield",
"state": "IL",
"postalCode": "62701"
}
]
}
Suchen nach Ressourcen
Patienten nach Namen suchen:
const searchPatients = async (searchParams) => {
const query = new URLSearchParams();
// Suchparameter hinzufügen
if (searchParams.name) {
query.append('name', searchParams.name);
}
if (searchParams.birthDate) {
query.append('birthdate', searchParams.birthDate);
}
if (searchParams.identifier) {
query.append('identifier', searchParams.identifier);
}
if (searchParams.gender) {
query.append('gender', searchParams.gender);
}
const response = await fhirRequest(`/Patient?${query.toString()}`);
return response;
};
// Verwendung
const results = await searchPatients({ name: 'Smith', birthDate: '1985-06-15' });
console.log(`Gefunden ${results.total} Patienten`);
results.entry.forEach(entry => {
const patient = entry.resource;
console.log(`${patient.name[0].family}, ${patient.name[0].given[0]}`);
});
Häufige Suchparameter
| Ressource | Suchparameter | Beispiel |
|---|---|---|
| Patient | name, birthdate, identifier, gender, phone, email | ?name=Smith&birthdate=1985-06-15 |
| Observation | patient, code, date, category, status | ?patient=123&code=8480-6&date=ge2026-01-01 |
| Condition | patient, clinical-status, category, onset-date | ?patient=123&clinical-status=active |
| MedicationRequest | patient, status, intent, medication | ?patient=123&status=active |
| Encounter | patient, date, status, class | ?patient=123&date=ge2026-01-01 |
| DiagnosticReport | patient, category, date, status | ?patient=123&category=laboratory |
Suchmodifikatoren
| Modifikator | Beschreibung | Beispiel |
|---|---|---|
:exact |
Exakte Übereinstimmung | name:exact=Smith |
:contains |
Enthält | name:contains=smi |
:missing |
Hat/Fehlender Wert | phone:missing=true |
: (Präfix) |
Präfix-Operatoren | birthdate=ge1980-01-01 |
Suchpräfixe für Daten und Zahlen
| Präfix | Bedeutung | Beispiel |
|---|---|---|
eq |
Gleich | birthdate=eq1985-06-15 |
ne |
Ungleich | birthdate=ne1985-06-15 |
gt |
Größer als | birthdate=gt1980-01-01 |
lt |
Kleiner als | birthdate=lt1990-01-01 |
ge |
Größer oder gleich | birthdate=ge1980-01-01 |
le |
Kleiner oder gleich | birthdate=le1990-01-01 |
sa |
Beginnt nach | date=sa2026-01-01 |
eb |
Endet vor | date=eb2026-12-31 |
Arbeiten mit klinischen Daten
Eine Beobachtung erstellen (Vitalwerte)
Vitalwerte aufzeichnen:
const createObservation = async (observationData) => {
const observation = {
resourceType: 'Observation',
status: 'final',
category: [
{
coding: [{
system: 'http://terminology.hl7.org/CodeSystem/observation-category',
code: 'vital-signs'
}]
}
],
code: {
coding: [{
system: 'http://loinc.org',
code: observationData.code, // z.B. '8480-6' für systolischen Blutdruck
display: observationData.display
}]
},
subject: {
reference: `Patient/${observationData.patientId}`
},
effectiveDateTime: observationData.effectiveDate || new Date().toISOString(),
valueQuantity: {
value: observationData.value,
unit: observationData.unit,
system: 'http://unitsofmeasure.org',
code: observationData.ucumCode
},
performer: [
{
reference: `Practitioner/${observationData.performerId}`
}
]
};
const response = await fhirRequest('/Observation', {
method: 'POST',
body: JSON.stringify(observation)
});
return response;
};
// Verwendung - Blutdruck aufzeichnen
const systolicBP = await createObservation({
patientId: '12345',
code: '8480-6',
display: 'Systolischer Blutdruck',
value: 120,
unit: 'mmHg',
ucumCode: 'mm[Hg]',
performerId: '67890'
});
console.log(`Beobachtung erstellt: ${systolicBP.id}`);
Häufige LOINC-Codes
| Code | Anzeige | Kategorie |
|---|---|---|
| 8480-6 | Systolischer Blutdruck | Vitalwerte |
| 8462-4 | Diastolischer Blutdruck | Vitalwerte |
| 8867-4 | Herzfrequenz | Vitalwerte |
| 8310-5 | Körpertemperatur | Vitalwerte |
| 8302-2 | Körpergröße | Vitalwerte |
| 29463-7 | Körpergewicht | Vitalwerte |
| 8871-5 | Atemfrequenz | Vitalwerte |
| 2339-0 | Glukose [Masse/Volumen] | Labor |
| 4548-4 | Hämoglobin A1c | Labor |
| 2093-3 | Cholesterin [Masse/Volumen] | Labor |
Eine Bedingung erstellen (Eintrag in der Problemliste)
Diagnose zur Problemliste hinzufügen:
const createCondition = async (conditionData) => {
const condition = {
resourceType: 'Condition',
clinicalStatus: {
coding: [{
system: 'http://terminology.hl7.org/CodeSystem/condition-clinical',
code: conditionData.status || '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: conditionData.category || 'problem-list-item'
}]
}
],
code: {
coding: [{
system: 'http://snomed.info/sct',
code: conditionData.sctCode,
display: conditionData.display
}]
},
subject: {
reference: `Patient/${conditionData.patientId}`
},
onsetDateTime: conditionData.onsetDate,
recordedDate: new Date().toISOString()
};
const response = await fhirRequest('/Condition', {
method: 'POST',
body: JSON.stringify(condition)
});
return response;
};
// Verwendung - Diabetes zur Problemliste hinzufügen
const diabetes = await createCondition({
patientId: '12345',
sctCode: '44054006',
display: 'Diabetes mellitus Typ 2',
status: 'active',
category: 'problem-list-item',
onsetDate: '2024-01-15'
});
Häufige SNOMED CT-Codes
| Code | Anzeige | Kategorie |
|---|---|---|
| 44054006 | Diabetes mellitus Typ 2 | Problem |
| 38341003 | Hypertonie | Problem |
| 195967001 | Asthma | Problem |
| 13645005 | Chronisch obstruktive Lungenerkrankung | Problem |
| 35489007 | Depressive Störung | Problem |
| 22298006 | Myokardinfarkt | Problem |
| 26929004 | Alzheimer-Krankheit | Problem |
| 396275006 | Arthrose | Problem |
Medikationen des Patienten abrufen
Aktive Medikationsanfragen abrufen:
const getPatientMedications = async (patientId) => {
const response = await fhirRequest(
`/MedicationRequest?patient=${patientId}&status=active`
);
return response;
};
// Verwendung
const medications = await getPatientMedications('12345');
medications.entry?.forEach(entry => {
const med = entry.resource;
console.log(`${med.medicationCodeableConcept.coding[0].display}`);
console.log(` Dosis: ${med.dosageInstruction[0]?.text}`);
console.log(` Status: ${med.status}`);
});
Laborergebnisse abrufen
Diagnoseberichte und Beobachtungen abrufen:
const getPatientLabResults = async (patientId, options = {}) => {
const params = new URLSearchParams({
patient: patientId,
category: options.category || 'laboratory'
});
if (options.dateFrom) {
params.append('date', `ge${options.dateFrom}`);
}
const response = await fhirRequest(`/DiagnosticReport?${params.toString()}`);
return response;
};
// Spezifischen Labortest abrufen (z.B. HbA1c)
const getLabValue = async (patientId, loincCode) => {
const params = new URLSearchParams({
patient: patientId,
code: loincCode
});
const response = await fhirRequest(`/Observation?${params.toString()}`);
return response;
};
// Verwendung - HbA1c-Ergebnisse abrufen
const hba1c = await getLabValue('12345', '4548-4');
hba1c.entry?.forEach(entry => {
const obs = entry.resource;
console.log(`HbA1c: ${obs.valueQuantity.value} ${obs.valueQuantity.unit}`);
console.log(`Datum: ${obs.effectiveDateTime}`);
});
OAuth 2.0 und SMART on FHIR
FHIR-Authentifizierung verstehen
FHIR-Server verwenden OAuth 2.0 mit OpenID Connect:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │───▶│ Auth │───▶│ FHIR │
│ (App) │ │ Server │ │ Server │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ 1. Auth-Anfrage │ │
│───────────────────▶│ │
│ │ │
│ 2. Benutzer-Login │ │
│◀───────────────────│ │
│ │ │
│ 3. Auth-Code │ │
│───────────────────▶│ │
│ │ │
│ 4. Token-Anfrage │ │
│───────────────────▶│ │
│ │ 5. Token + ID │
│◀───────────────────│ │
│ │ │
│ 6. API-Anfrage │ │
│────────────────────────────────────────▶│
│ │ │
│ 7. FHIR-Daten │ │
│◀────────────────────────────────────────│
SMART on FHIR App-Start
SMART-App-Start implementieren:
const crypto = require('crypto');
class SMARTClient {
constructor(config) {
this.clientId = config.clientId;
this.redirectUri = config.redirectUri;
this.issuer = config.issuer; // FHIR-Server-URL
this.scopes = config.scopes;
}
generatePKCE() {
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
return { codeVerifier, codeChallenge };
}
buildAuthUrl(state, patientId = null) {
const { codeVerifier, codeChallenge } = this.generatePKCE();
// codeVerifier für den Token-Austausch speichern
this.codeVerifier = codeVerifier;
const params = new URLSearchParams({
response_type: 'code',
client_id: this.clientId,
redirect_uri: this.redirectUri,
scope: this.scopes.join(' '),
state: state,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
aud: this.issuer,
launch: patientId ? `patient-${patientId}` : null
});
return `${this.issuer}/authorize?${params.toString()}`;
}
async exchangeCodeForToken(code) {
const response = await fetch(`${this.issuer}/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: this.redirectUri,
client_id: this.clientId,
code_verifier: this.codeVerifier
})
});
const data = await response.json();
return {
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiresIn: data.expires_in,
patientId: data.patient,
encounterId: data.encounter
};
}
}
// Verwendung
const smartClient = new SMARTClient({
clientId: 'my-app-client-id',
redirectUri: 'https://myapp.com/callback',
issuer: 'https://fhir.epic.com',
scopes: [
'openid',
'profile',
'patient/Patient.read',
'patient/Observation.read',
'patient/Condition.read',
'patient/MedicationRequest.read',
'offline_access'
]
});
// Benutzer zur Auth-URL weiterleiten
const state = crypto.randomBytes(16).toString('hex');
const authUrl = smartClient.buildAuthUrl(state);
console.log(`Weiterleiten zu: ${authUrl}`);
Erforderliche SMART-Scopes
| Scope | Berechtigung | Anwendungsfall |
|---|---|---|
openid |
OIDC-Authentifizierung | Für alle Apps erforderlich |
profile |
Benutzerprofilinformationen | Anbieterverzeichnis |
patient/Patient.read |
Patientendemografie lesen | Patientensuche |
patient/Observation.read |
Beobachtungen lesen | Vitalwerte, Labore |
patient/Condition.read |
Bedingungen lesen | Problemlisten |
patient/MedicationRequest.read |
Medikationen lesen | Medikationshistorie |
patient/*.read |
Alle Patientenressourcen lesen | Vollständige Patientendaten |
user/*.read |
Alle zugänglichen Ressourcen lesen | Anbieteransicht |
offline_access |
Refresh Token | Langlebige Sitzungen |
Authentifizierte FHIR-Anfragen stellen
class FHIRClient {
constructor(accessToken, fhirBaseUrl) {
this.accessToken = accessToken;
this.baseUrl = fhirBaseUrl;
}
async request(endpoint, options = {}) {
const response = await fetch(`${this.baseUrl}/fhir${endpoint}`, {
...options,
headers: {
'Accept': 'application/fhir+json',
'Authorization': `Bearer ${this.accessToken}`,
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(`FHIR-Fehler: ${error.issue?.[0]?.details?.text}`);
}
return response.json();
}
async getPatient(patientId) {
return this.request(`/Patient/${patientId}`);
}
async searchPatients(params) {
const query = new URLSearchParams(params);
return this.request(`/Patient?${query.toString()}`);
}
}
// Verwendung nach OAuth-Callback
const fhirClient = new FHIRClient(tokens.accessToken, 'https://fhir.epic.com');
const patient = await fhirClient.getPatient(tokens.patientId);
Batch- und Transaktionsoperationen
Batch-Anfragen
Mehrere unabhängige Anfragen ausführen:
const batchRequest = async (requests) => {
const bundle = {
resourceType: 'Bundle',
type: 'batch',
entry: requests.map(req => ({
resource: req.resource,
request: {
method: req.method,
url: req.url
}
}))
};
const response = await fhirRequest('', {
method: 'POST',
body: JSON.stringify(bundle)
});
return response;
};
// Verwendung - Mehrere Ressourcen abrufen
const bundle = await batchRequest([
{ method: 'GET', url: 'Patient/12345' },
{ method: 'GET', url: 'Patient/12345/Observation?category=vital-signs' },
{ method: 'GET', url: 'Patient/12345/Condition?clinical-status=active' }
]);
bundle.entry.forEach((entry, index) => {
console.log(`Antwort ${index}: ${entry.response.status}`);
console.log(entry.resource);
});
Transaktionsanfragen
Mehrere Anfragen als atomare Einheit ausführen:
const transactionRequest = async (requests) => {
const bundle = {
resourceType: 'Bundle',
type: 'transaction',
entry: requests.map(req => ({
resource: req.resource,
request: {
method: req.method,
url: req.url
}
}))
};
const response = await fhirRequest('', {
method: 'POST',
body: JSON.stringify(bundle)
});
return response;
};
// Verwendung - Patient und zugehörige Ressourcen erstellen
const transaction = await transactionRequest([
{
method: 'POST',
url: 'Patient',
resource: {
resourceType: 'Patient',
name: [{ family: 'Doe', given: ['Jane'] }],
gender: 'female',
birthDate: '1990-01-01'
}
},
{
method: 'POST',
url: 'Condition',
resource: {
resourceType: 'Condition',
clinicalStatus: { coding: [{ code: 'active' }] },
code: { coding: [{ system: 'http://snomed.info/sct', code: '38341003' }] },
subject: { reference: 'Patient/-1' } // Referenz zur ersten Ressource
}
}
]);
Abonnements und Webhooks
FHIR-Abonnements (R4B+)
Ressourcenänderungen abonnieren:
const createSubscription = async (subscriptionData) => {
const subscription = {
resourceType: 'Subscription',
status: 'requested',
criteria: subscriptionData.criteria,
reason: subscriptionData.reason,
channel: {
type: 'rest-hook',
endpoint: subscriptionData.endpoint,
payload: 'application/fhir+json'
}
};
const response = await fhirRequest('/Subscription', {
method: 'POST',
body: JSON.stringify(subscription)
});
return response;
};
// Verwendung - Neue Laborergebnisse abonnieren
const subscription = await createSubscription({
criteria: 'DiagnosticReport?category=laboratory&patient=12345',
reason: 'Laborergebnisse des Patienten überwachen',
endpoint: 'https://myapp.com/webhooks/fhir'
});
FHIR-Webhooks behandeln
const express = require('express');
const app = express();
app.post('/webhooks/fhir', express.json({ type: 'application/fhir+json' }), async (req, res) => {
const notification = req.body;
// Abonnementreferenz überprüfen
if (notification.subscription !== expectedSubscription) {
return res.status(401).send('Nicht autorisiert');
}
// Benachrichtigung verarbeiten
if (notification.event?.resourceType === 'DiagnosticReport') {
const reportId = notification.event.resourceId;
const report = await fhirRequest(`/DiagnosticReport/${reportId}`);
// Neues Laborergebnis verarbeiten
await processLabResult(report);
}
res.status(200).send('OK');
});
Häufige Probleme beheben
Problem: 401 Nicht autorisiert
Symptome: Erhalten von "Nicht autorisiert" oder "Ungültiges Token"-Fehlern.
Lösungen:
- Überprüfen, ob das Token nicht abgelaufen ist
- Überprüfen, ob der Token-Scope die angeforderte Ressource enthält
- Sicherstellen, dass der Header
Authorization: Bearer {token}vorhanden ist - Überprüfen, ob die FHIR-Server-URL mit der Token-Zielgruppe übereinstimmt
Problem: 403 Verboten
Symptome: Token ist gültig, aber der Zugriff wird verweigert.
Lösungen:
- Überprüfen, ob der Benutzer die Berechtigung für die angeforderte Ressource hat
- Überprüfen, ob der Patientenkontxt übereinstimmt (für patientenbezogene Tokens)
- Sicherstellen, dass die SMART-Scopes die angeforderte Operation enthalten
- Zugriffskontrollen auf Ressourcenebene überprüfen
Problem: 404 Nicht gefunden
Symptome: Ressource existiert nicht oder der Endpunkt ist falsch.
Lösungen:
- Überprüfen, ob die Ressourcen-ID korrekt ist
- Überprüfen, ob die FHIR-Basis-URL korrekt ist
- Sicherstellen, dass der Ressourcentyp vom Server unterstützt wird
- Versionsspezifischen Endpunkt überprüfen (R4 vs R4B)
Problem: 422 Unverarbeitbare Entität
Symptome: Validierungsfehler beim Erstellen/Aktualisieren.
Lösungen:
// Validierungsfehler parsen
const error = await response.json();
error.issue?.forEach(issue => {
console.log(`Schweregrad: ${issue.severity}`);
console.log(`Ort: ${issue.expression?.join('.')}`);
console.log(`Nachricht: ${issue.details?.text}`);
});
Häufige Ursachen:
- Fehlende Pflichtfelder
- Ungültige Codesystemwerte
- Falsches Referenzformat
- Probleme mit dem Datumsformat
Checkliste für die Produktionsbereitstellung
Vor der Liveschaltung:
- [ ] OAuth 2.0 mit SMART on FHIR konfigurieren
- [ ] Token-Refresh-Logik implementieren
- [ ] Ordentliche Fehlerbehandlung einrichten
- [ ] Umfassende Protokollierung hinzufügen (keine PHI in Protokollen)
- [ ] Ratenbegrenzung implementieren
- [ ] Wiederholungslogik mit exponentiellem Backoff konfigurieren
- [ ] Mit mehreren EHR-Anbietern testen
- [ ] Gegen FHIR-Validator validieren
- [ ] Alle FHIR-Operationen dokumentieren
- [ ] Überwachung und Alarmierung einrichten
- [ ] Runbook für häufige Probleme erstellen
FHIR-Validierung
const { FhirValidator } = require('fhir-validator');
const validator = new FhirValidator('4.0.1');
const validateResource = async (resource) => {
const validationResult = await validator.validate(resource);
if (!validationResult.valid) {
validationResult.issues.forEach(issue => {
console.error(`Validierungsfehler: ${issue.message}`);
console.error(`Ort: ${issue.path}`);
});
throw new Error('Ressourcenvalidierung fehlgeschlagen');
}
return true;
};
// Verwendung vor dem Erstellen/Aktualisieren
await validateResource(patientResource);
Anwendungsfälle aus der Praxis
Integration des Patientenportals
Ein Gesundheitssystem baut ein Patientenportal auf:
- Herausforderung: Patienten konnten nicht auf Akten von mehreren Anbietern zugreifen
- Lösung: SMART on FHIR App mit Epic- und Cerner-Integration
- Ergebnis: 80 % Patientenakzeptanz, 50 % Reduzierung der Aktenanfragen
Wichtige Implementierung:
- Patientenorientierter SMART-App-Start
- Lesezugriff auf Patient, Observation, Condition, MedicationRequest
- Refresh-Token für persistente Sitzungen
- Mobilfreundliche Benutzeroberfläche
Klinische Entscheidungsunterstützung
Eine Plattform für das Versorgungsmanagement fügt CDS hinzu:
- Herausforderung: Anbietern entgingen Möglichkeiten zur präventiven Versorgung
- Lösung: Echtzeit-FHIR-Abfragen nach Versorgungslücken
- Ergebnis: 25 % Verbesserung der HEDIS-Scores
Wichtige Implementierung:
- Anbieterorientierte SMART-App
- Abfrage von Patient, Condition, Observation, Immunization
- Berechnung von Versorgungslücken auf Basis von Leitlinien
- Inline-Empfehlungen im EHR-Workflow
Analysen zur Bevölkerungsgesundheit
Ein Kostenträger erstellt ein Dashboard zur Bevölkerungsgesundheit:
- Herausforderung: Unvollständige Daten über Anbieter-Netzwerke hinweg
- Lösung: FHIR-Bulk-Datenexport für Analysen
- Ergebnis: 360-Grad-Patientenansicht, reduzierte PMPM-Kosten
Wichtige Implementierung:
- FHIR Bulk Data Access ($export)
- Nächtliche Exporte an das Data Warehouse
- Risikostratifizierungsmodelle
- Benachrichtigungen des Pflegemanagers
Fazit
HL7 FHIR bildet die Grundlage für die moderne Interoperabilität im Gesundheitswesen. Wichtige Erkenntnisse:
- FHIR R4 ist der aktuelle Standard für die Integration von Gesundheits-APIs
- SMART on FHIR ermöglicht eine sichere OAuth 2.0-Authentifizierung
- Ressourcentypen standardisieren Patient, Beobachtung, Bedingung und Medikationsdaten
- Suchparameter ermöglichen flexible Abfragen
- Batch- und Transaktionsoperationen unterstützen komplexe Workflows
- Apidog optimiert FHIR API-Tests und -Dokumentation
FAQ-Bereich
Wofür wird HL7 FHIR verwendet?
FHIR ermöglicht den standardisierten Austausch von Gesundheitsdaten zwischen EHRs, Patientenportalen, mobilen Apps und anderen Gesundheits-IT-Systemen. Häufige Anwendungsfälle sind Patientenzugriffs-Apps, klinische Entscheidungsunterstützung, Bevölkerungsgesundheit und Versorgungskoordination.
Wie fange ich mit FHIR an?
Beginnen Sie mit dem Zugriff auf einen öffentlichen FHIR-Server (wie den HAPI FHIR-Testserver) oder richten Sie einen Cloud-FHIR-Dienst ein (Azure API for FHIR, AWS HealthLake). Üben Sie das Lesen von Ressourcen und die Verwendung von Suchparametern.
Was ist der Unterschied zwischen HL7 v2 und FHIR?
HL7 v2 verwendet durch Pipe getrennte Nachrichten (ADT, ORM, ORU) für den ereignisgesteuerten Datenaustausch. FHIR verwendet RESTful APIs mit JSON/XML für den ressourcenbasierten Zugriff. FHIR ist einfacher zu implementieren und besser für moderne Web-/Mobile-Apps geeignet.
Ist FHIR HIPAA-konform?
FHIR selbst ist ein Datenformatstandard. Die HIPAA-Konformität hängt von der Implementierung ab: Verschlüsselung, Authentifizierung, Zugriffskontrollen und Audit-Protokollierung. Verwenden Sie OAuth 2.0 mit SMART on FHIR für sicheren Zugriff.
Was sind SMART-Scopes?
SMART-Scopes definieren granulare Zugriffsrechte für FHIR-Ressourcen (z.B. patient/Observation.read, user/*.read). Fordern Sie nur die Scopes an, die Ihre App benötigt.
Wie suche ich nach Ressourcen in FHIR?
Verwenden Sie GET-Anfragen mit Abfrageparametern: /Patient?name=Smith&birthdate=ge1980-01-01. FHIR unterstützt Modifikatoren (:exact, :contains) und Präfixe (gt, lt, ge, le).
Was ist Bulk FHIR?
Bulk FHIR ($export) ermöglicht den asynchronen Export großer Datensätze im NDJSON-Format. Es wird für Bevölkerungsgesundheit, Analysen und Data Warehousing verwendet.
Wie gehe ich mit FHIR-Versionierung um?
Zielen Sie auf eine bestimmte FHIR-Version (R4 empfohlen) und verwenden Sie versionsspezifische Endpunkte. Überprüfen Sie das CapabilityStatement auf unterstützte Versionen und Ressourcen.
Kann ich FHIR mit benutzerdefinierten Feldern erweitern?
Ja, verwenden Sie FHIR-Erweiterungen, um benutzerdefinierte Datenelemente hinzuzufügen. Definieren Sie Erweiterungen in Ihrem Implementation Guide und registrieren Sie sie bei HL7, wenn Sie sie breit teilen möchten.
Welche Tools helfen bei der FHIR-Entwicklung?
Beliebte Tools sind HAPI FHIR (Open-Source-Server), FHIR-Validator, Postman-Sammlungen und Apidog für API-Tests und -Dokumentation.
