DEV Community

Cover image for HL7 FHIR API の使い方:完全医療連携ガイド (2026年)
Akira
Akira

Posted on • Originally published at apidog.com

HL7 FHIR API の使い方:完全医療連携ガイド (2026年)

TL;DR

HL7 FHIR(Fast Healthcare Interoperability Resources)は、RESTful APIとJSON/XMLレスポンスを活用した医療データ交換の最新標準です。患者情報や観察結果、薬剤など標準化されたリソースを提供し、OAuth 2.0認証とアプリ統合用SMART on FHIRを組み込みます。本ガイドは、FHIRのアーキテクチャ、リソースタイプ、検索パラメーター、認証、本番導入戦略を実装観点で解説します。

Apidogを今すぐ試してみよう

はじめに

医療データの断片化により、米国医療システムは毎年300億ドル以上のコストを強いられています。開発者にとって、HL7 FHIR APIの統合はCMSで義務化され、EpicやCernerなど主要EHRベンダーも標準で採用しています。

実際、FHIR対応アプリによる医療現場では、ケア連携時間が40%短縮され、FAXによる記録要求が85%削減されています。強固なFHIR API連携は、EHR、患者ポータル、ケア連携プラットフォーム間でのデータ交換をシームレスにします。

このガイドは、HL7 FHIR APIの統合プロセスを、実装手順・コード例を交えて網羅します。FHIRのアーキテクチャ、リソースタイプ、検索パラメーター、OAuth 2.0認証、SMART on FHIR連携、本番環境デプロイまで、実践的に解説します。本番運用可能なFHIR統合を構築しましょう。

💡 Apidogは医療API統合を効率化

FHIRエンドポイントのテスト、スキーマ検証、認証フローのデバッグ、API仕様のドキュメント化を1つのワークスペースで。FHIR実装ガイドのインポート、モックレスポンス作成、テストシナリオ共有も可能。

HL7 FHIRとは?

FHIR (Fast Healthcare Interoperability Resources) は、Health Level Seven International (HL7) によって策定された、電子的な医療情報交換の標準フレームワークです。RESTful API、JSON/XML、OAuth 2.0など最新Web技術を活用できます。

FHIR概要

FHIRリソースタイプ

FHIRは140以上のリソースタイプを持ち、以下が主要コアリソースです。

リソース 目的 一般的なユースケース
Patient デモグラフィック情報 患者検索、登録
Practitioner 医療従事者情報 ディレクトリ、スケジューリング
Encounter 受診/入院 ケアエピソード、請求
Observation 臨床データ バイタル、検査結果、評価
Condition 問題/診断 問題リスト、ケアプランニング
MedicationRequest 処方箋 e-処方、薬剤履歴
AllergyIntolerance アレルギー 安全確認、アラート
Immunization 予防接種 予防接種記録
DiagnosticReport 検査/画像診断レポート 結果配信
DocumentReference 臨床文書 CCD、退院サマリー

FHIR APIアーキテクチャ

FHIRはRESTful APIパターンでリソースを操作します。

https://fhir-server.com/fhir/{resourceType}/{id}
Enter fullscreen mode Exit fullscreen mode

FHIRバージョン比較

バージョン ステータス ユースケース
R4 (4.0.1) 現在のSTU 本番環境での実装
R4B (4.3) 試用実装 早期導入者
R5 (5.0.0) ドラフトSTU 将来の実装
DSTU2 非推奨 レガシーシステムのみ

CMSは認定EHRに対し、患者・医療提供者アクセスAPIでFHIR R4対応を必須としています。

開始する:FHIRサーバーへのアクセス

ステップ1:FHIRサーバーの選択

以下のいずれかのサーバーを用途・コスト・運用形態に応じて選択します。

サーバー タイプ コスト 最適な用途
Azure API for FHIR マネージド 従量課金制 エンタープライズ、Azureユーザー
AWS HealthLake マネージド 従量課金制 AWS環境
Google Cloud Healthcare API マネージド 従量課金制 GCP環境
HAPI FHIR オープンソース セルフホスト カスタムデプロイ
Epic FHIR Server 商用 Epic顧客 Epic EHRとの統合
Cerner Ignite FHIR 商用 Cerner顧客 Cerner EHRとの統合

ステップ2:サーバー認証情報の取得

クラウドFHIRサービスの場合の認証情報取得例:

# Azure API for FHIR
# 1. Azure PortalでFHIRサービスを作成
# 2. 認証を構成 (OAuth 2.0 または AAD)
# 3. FHIRエンドポイントを取得: https://{service-name}.azurehealthcareapis.com
# 4. OAuth用のクライアントアプリを登録

# AWS HealthLake
# 1. AWSコンソールでデータストアを作成
# 2. IAMロールを構成
# 3. エンドポイントを取得: https://healthlake.{region}.amazonaws.com
Enter fullscreen mode Exit fullscreen mode

ステップ3:FHIR RESTful操作の理解

FHIRは標準HTTPメソッドでリソースを操作します。

操作 HTTPメソッド エンドポイント 説明
Read GET /{resourceType}/{id} 特定のリソースを取得
Search GET /{resourceType}?param=value リソースを検索
Create POST /{resourceType} 新しいリソースを作成
Update PUT /{resourceType}/{id} リソースを置き換え
Patch PATCH /{resourceType}/{id} 部分的な更新
Delete DELETE /{resourceType}/{id} リソースを削除
History GET /{resourceType}/{id}/_history リソースのバージョン履歴

ステップ4:最初のFHIR呼び出しを行う

エンドポイントが正しく動作するか、最初のリクエストで確認します。

curl -X GET "https://fhir-server.com/fhir/metadata" \
  -H "Accept: application/fhir+json" \
  -H "Authorization: Bearer {token}"
Enter fullscreen mode Exit fullscreen mode

期待されるレスポンス例:

{
  "resourceType": "CapabilityStatement",
  "status": "active",
  "date": "2026-03-25",
  "fhirVersion": "4.0.1",
  "rest": [{
    "mode": "server",
    "resource": [
      { "type": "Patient" },
      { "type": "Observation" },
      { "type": "Condition" }
    ]
  }]
}
Enter fullscreen mode Exit fullscreen mode

コアFHIR操作

患者リソースの読み取り

患者IDでリソースを取得する実装例:

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 Error: ${error.issue?.[0]?.details?.text || response.statusText}`);
  }

  return response.json();
};

const getPatient = async (patientId) => {
  const patient = await fhirRequest(`/Patient/${patientId}`);
  return patient;
};

const patient = await getPatient('12345');
console.log(`Patient: ${patient.name[0].given[0]} ${patient.name[0].family}`);
console.log(`DOB: ${patient.birthDate}`);
console.log(`Gender: ${patient.gender}`);
Enter fullscreen mode Exit fullscreen mode

患者リソースの構造

{
  "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"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

リソースの検索

患者名・生年月日などで検索するコード例:

const searchPatients = async (searchParams) => {
  const query = new URLSearchParams();

  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;
};

const results = await searchPatients({ name: 'Smith', birthDate: '1985-06-15' });

console.log(`Found ${results.total} patients`);
results.entry.forEach(entry => {
  const patient = entry.resource;
  console.log(`${patient.name[0].family}, ${patient.name[0].given[0]}`);
});
Enter fullscreen mode Exit fullscreen mode

共通の検索パラメーター

リソース 検索パラメーター
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

検索修飾子

修飾子 説明
:exact 完全一致 name:exact=Smith
:contains 部分一致 name:contains=smi
:missing 値の有無 phone:missing=true
: (プレフィックス) プレフィックス演算子 birthdate=ge1980-01-01

日付・数値の検索プレフィックス

プレフィックス 意味
eq 等しい birthdate=eq1985-06-15
ne 等しくない birthdate=ne1985-06-15
gt より大きい birthdate=gt1980-01-01
lt より小さい birthdate=lt1990-01-01
ge 以上 birthdate=ge1980-01-01
le 以下 birthdate=le1990-01-01
sa より後 date=sa2026-01-01
eb より前 date=eb2026-12-31

臨床データの操作

Observation(バイタルサイン)の作成

バイタル値(例:血圧)を記録する例:

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,
        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;
};

// 使用例
const systolicBP = await createObservation({
  patientId: '12345',
  code: '8480-6',
  display: 'Systolic blood pressure',
  value: 120,
  unit: 'mmHg',
  ucumCode: 'mm[Hg]',
  performerId: '67890'
});

console.log(`Observation created: ${systolicBP.id}`);
Enter fullscreen mode Exit fullscreen mode

一般的なLOINCコード

コード 表示 カテゴリ
8480-6 収縮期血圧 バイタルサイン
8462-4 拡張期血圧 バイタルサイン
8867-4 心拍数 バイタルサイン
8310-5 体温 バイタルサイン
8302-2 身長 バイタルサイン
29463-7 体重 バイタルサイン
8871-5 呼吸数 バイタルサイン
2339-0 血糖 [質量/容積] 検査
4548-4 ヘモグロビンA1c 検査
2093-3 コレステロール [質量/容積] 検査

Condition(問題リスト項目)の作成

患者の問題リストに診断を追加:

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;
};

// 使用例
const diabetes = await createCondition({
  patientId: '12345',
  sctCode: '44054006',
  display: 'Type 2 Diabetes Mellitus',
  status: 'active',
  category: 'problem-list-item',
  onsetDate: '2024-01-15'
});
Enter fullscreen mode Exit fullscreen mode

一般的なSNOMED CTコード

コード 表示 カテゴリ
44054006 2型糖尿病 問題
38341003 高血圧 問題
195967001 喘息 問題
13645005 慢性閉塞性肺疾患 問題
35489007 うつ病性障害 問題
22298006 心筋梗塞 問題
26929004 アルツハイマー病 問題
396275006 変形性関節症 問題

患者の薬剤情報の取得

アクティブな薬剤依頼を取得:

const getPatientMedications = async (patientId) => {
  const response = await fhirRequest(
    `/MedicationRequest?patient=${patientId}&status=active`
  );
  return response;
};

const medications = await getPatientMedications('12345');
medications.entry?.forEach(entry => {
  const med = entry.resource;
  console.log(`${med.medicationCodeableConcept.coding[0].display}`);
  console.log(`  Dose: ${med.dosageInstruction[0]?.text}`);
  console.log(`  Status: ${med.status}`);
});
Enter fullscreen mode Exit fullscreen mode

検査結果の取得

検査レポートや観察値を取得する例:

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;
};

const getLabValue = async (patientId, loincCode) => {
  const params = new URLSearchParams({
    patient: patientId,
    code: loincCode
  });

  const response = await fhirRequest(`/Observation?${params.toString()}`);
  return response;
};

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(`Date: ${obs.effectiveDateTime}`);
});
Enter fullscreen mode Exit fullscreen mode

OAuth 2.0とSMART on FHIR

FHIR認証の理解

FHIRサーバーはOAuth 2.0(OpenID Connect付き)による認証を採用します。

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   クライアント    │───▶│   認証        │───▶│   FHIR        │
│   (アプリ)    │    │   サーバー    │    │   サーバー    │
└─────────────┘    └─────────────┘    └─────────────┘
     │                    │                    │
     │  1. 認証リクエスト  │                    │
     │───────────────────▶│                    │
     │                    │                    │
     │  2. ユーザーログイン │                    │
     │◀───────────────────│                    │
     │                    │                    │
     │  3. 認証コード     │                    │
     │───────────────────▶│                    │
     │                    │                    │
     │  4. トークンリクエスト │                    │
     │───────────────────▶│                    │
     │                    │  5. トークン + ID │
     │◀───────────────────│                    │
     │                    │                    │
     │  6. APIリクエスト  │                    │
     │────────────────────────────────────────▶│
     │                    │                    │
     │  7. FHIRデータ     │                    │
     │◀────────────────────────────────────────│
Enter fullscreen mode Exit fullscreen mode

SMART on FHIRアプリの起動

実装例(PKCE対応):

const crypto = require('crypto');

class SMARTClient {
  constructor(config) {
    this.clientId = config.clientId;
    this.redirectUri = config.redirectUri;
    this.issuer = config.issuer;
    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();
    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
    };
  }
}

// 使用例
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'
  ]
});

const state = crypto.randomBytes(16).toString('hex');
const authUrl = smartClient.buildAuthUrl(state);
console.log(`Redirect to: ${authUrl}`);
Enter fullscreen mode Exit fullscreen mode

必須SMARTスコープ

スコープ パーミッション ユースケース
openid OIDC認証 すべてのアプリに必須
profile ユーザープロファイル情報 医療従事者ディレクトリ
patient/Patient.read 患者デモグラフィック情報の読み取り 患者検索
patient/Observation.read 観察結果の読み取り バイタル、検査
patient/Condition.read 状態の読み取り 問題リスト
patient/MedicationRequest.read 薬剤情報の読み取り 薬剤履歴
patient/*.read すべての患者リソースの読み取り 患者データの全体像
user/*.read アクセス可能なすべてのリソースの読み取り 医療従事者ビュー
offline_access リフレッシュトークン 長時間セッション

認証済みFHIRリクエストの作成

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 Error: ${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()}`);
  }
}

// OAuthコールバック後の使用例
const fhirClient = new FHIRClient(tokens.accessToken, 'https://fhir.epic.com');
const patient = await fhirClient.getPatient(tokens.patientId);
Enter fullscreen mode Exit fullscreen mode

バッチおよびトランザクション操作

バッチリクエスト

複数の独立リクエストを一括で実行:

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;
};

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(`Response ${index}: ${entry.response.status}`);
  console.log(entry.resource);
});
Enter fullscreen mode Exit fullscreen mode

トランザクションリクエスト

複数リソースをアトミックに作成・更新:

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;
};

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' }
    }
  }
]);
Enter fullscreen mode Exit fullscreen mode

サブスクリプションとWebhook

FHIRサブスクリプション (R4B+)

リソースの変更通知を受信:

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;
};

const subscription = await createSubscription({
  criteria: 'DiagnosticReport?category=laboratory&patient=12345',
  reason: 'Monitor patient lab results',
  endpoint: 'https://myapp.com/webhooks/fhir'
});
Enter fullscreen mode Exit fullscreen mode

FHIR Webhookの処理

通知を受信して処理するExpress例:

const express = require('express');
const app = express();

app.post('/webhooks/fhir', express.json({ type: 'application/fhir+json' }), async (req, res) => {
  const notification = req.body;

  if (notification.subscription !== expectedSubscription) {
    return res.status(401).send('Unauthorized');
  }

  if (notification.event?.resourceType === 'DiagnosticReport') {
    const reportId = notification.event.resourceId;
    const report = await fhirRequest(`/DiagnosticReport/${reportId}`);
    await processLabResult(report);
  }

  res.status(200).send('OK');
});
Enter fullscreen mode Exit fullscreen mode

一般的な問題のトラブルシューティング

問題: 401 Unauthorized

症状: 「Unauthorized」や「Invalid token」エラー

対策:

  1. トークンの有効期限を確認
  2. スコープが必要なリソースをカバーしているか確認
  3. Authorizationヘッダー付与を確認
  4. FHIRサーバーURLとトークンのaudが一致しているか確認

問題: 403 Forbidden

症状: トークン有効だがアクセス拒否

対策:

  1. ユーザー権限を確認
  2. 患者コンテキストの一致を確認
  3. SMARTスコープが操作対象を含むか確認
  4. リソースレベルACLの確認

問題: 404 Not Found

症状: リソースやエンドポイントが存在しない

対策:

  1. リソースIDの正確性を確認
  2. FHIRベースURLの正確性を確認
  3. サーバーでタイプがサポートされているか確認
  4. バージョン固有URL(R4/R4B等)を確認

問題: 422 Unprocessable Entity

症状: 作成/更新時のバリデーションエラー

対策/例:

const error = await response.json();
error.issue?.forEach(issue => {
  console.error(`Severity: ${issue.severity}`);
  console.error(`Location: ${issue.expression?.join('.')}`);
  console.error(`Message: ${issue.details?.text}`);
});
Enter fullscreen mode Exit fullscreen mode

主な原因例:

  • 必須フィールド欠落
  • コードシステム値不正
  • 参照形式ミス
  • 日付形式の誤り

本番デプロイチェックリスト

本番運用前の実装必須項目:

  • [ ] SMART on FHIRでOAuth 2.0認証を構成
  • [ ] トークンリフレッシュロジック実装
  • [ ] エラー処理の整備
  • [ ] PHIを含まない包括的なロギング
  • [ ] レートリミット導入
  • [ ] 指数バックオフ付きリトライ実装
  • [ ] 複数EHRベンダーでのテスト
  • [ ] FHIRバリデーターでリソース検証
  • [ ] FHIR操作のドキュメント化
  • [ ] 監視・アラート設定
  • [ ] トラブル対策ランブック作成

FHIR検証

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(`Validation Error: ${issue.message}`);
      console.error(`Location: ${issue.path}`);
    });
    throw new Error('Resource validation failed');
  }

  return true;
};

await validateResource(patientResource);
Enter fullscreen mode Exit fullscreen mode

実世界のユースケース

患者ポータル統合

  • 課題: 複数医療機関の記録にアクセス不可
  • 解決: Epic/Cerner統合SMART on FHIRアプリ
  • 結果: 患者利用率80%、記録要求50%削減

実装ポイント:

  • SMARTアプリ起動
  • Patient, Observation, Condition, MedicationRequestのreadのみ許可
  • リフレッシュトークンで永続セッション
  • モバイルUI

臨床意思決定支援

  • 課題: 予防ケアの機会逸失
  • 解決: リアルタイムFHIRクエリでケアギャップ検出
  • 結果: HEDISスコア25%向上

実装ポイント:

  • 医療従事者向けSMARTアプリ
  • Patient, Condition, Observation, Immunizationをクエリ
  • ガイドライン準拠のケアギャップ計算
  • EHR内でインライン推奨

集団健康分析

  • 課題: プロバイダ間データ不完全
  • 解決: FHIRバルクデータエクスポート利用
  • 結果: 360度患者ビュー、PMPMコスト削減

実装ポイント:

  • $exportによるバルクデータアクセス
  • データウェアハウスに毎晩エクスポート
  • リスク層別化モデル
  • ケアマネージャーへのアラート

結論

HL7 FHIRは現代医療システムの相互運用性を支えます。

主なポイント:

  • FHIR R4が現行の標準
  • SMART on FHIRでセキュアなOAuth 2.0認証
  • 標準リソース型(患者・観察・状態・薬剤)による一貫したデータアクセス
  • 柔軟な検索パラメーター
  • バッチ/トランザクションで業務ワークフロー自動化
  • Apidogで効率的なFHIR APIテスト・ドキュメント化

FAQ

HL7 FHIRは何のために使われますか?

FHIRは電子カルテ、患者ポータル、モバイルアプリ等の間で標準化された医療データ交換を実現します。代表的用途は患者アクセス、臨床意思決定支援、集団健康、ケア連携など。

FHIRを始めるにはどうすればよいですか?

公開FHIRサーバー(HAPI FHIRテストサーバー等)にアクセス、またはクラウドFHIRサービス(Azure API for FHIR、AWS HealthLake等)をセットアップし、リソース読み取りや検索クエリを試してください。

HL7 v2とFHIRの違いは?

HL7 v2はイベント駆動・パイプ区切りのメッセージ交換(ADT等)、FHIRはリソースベースRESTful API+JSON/XML。FHIRはWeb/モバイルアプリ連携に最適です。

FHIRはHIPAA準拠ですか?

FHIR自体はデータ形式標準で、HIPAA準拠は実装依存です。暗号化/認証/アクセス制御/監査を必ず実装してください。セキュアなアクセスにはSMART on FHIR+OAuth 2.0を推奨。

SMARTスコープとは?

SMARTスコープはFHIRリソースごとに細かいアクセス権を付与します(例: patient/Observation.read)。必要最小限のスコープのみ要求しましょう。

FHIRでリソースを検索するには?

GETクエリパラメータを使います。例:/Patient?name=Smith&birthdate=ge1980-01-01

修飾子(:exact, :contains)やプレフィックス(gt, lt, ge, le)も利用できます。

Bulk FHIRとは?

Bulk FHIR($export)は、NDJSON形式で大規模データセットを非同期エクスポートする仕組みです。集団分析・データウェアハウス用。

FHIRのバージョン管理は?

特定バージョン(基本はR4)をターゲットに、バージョン固有エンドポイントを利用。CapabilityStatementでサポート内容を確認しましょう。

FHIRをカスタム拡張できますか?

はい。FHIR拡張でカスタムデータ要素を追加可能。Implementation Guideで定義し、広く共有する場合はHL7に登録します。

FHIR開発に役立つツールは?

HAPI FHIR(OSSサーバー)、FHIRバリデーター、Postmanコレクション、Apidog(APIテスト・ドキュメント化)などが利用できます。

Top comments (0)