HL7 FHIR API 사용법: 완벽한 의료 통합 가이드 (2026)

Ashley Innocent

Ashley Innocent

25 March 2026

HL7 FHIR API 사용법: 완벽한 의료 통합 가이드 (2026)

Apidog 엔터프라이즈

온프레미스 배포

SSO & RBAC

SOC 2 준수

Apidog Enterprise 살펴보기

요약 (TL;DR)

HL7 FHIR (Fast Healthcare Interoperability Resources)는 RESTful API와 JSON/XML 응답을 사용하여 의료 데이터 교환을 위한 최신 표준입니다. 이것은 OAuth 2.0 인증 및 앱 통합을 위한 SMART on FHIR과 함께 환자, 관찰 기록, 약물 등과 같은 표준화된 리소스를 제공합니다. 이 가이드는 FHIR 아키텍처, 리소스 유형, 검색 매개변수, 인증 및 프로덕션 구현 전략을 다룹니다.

서론

의료 데이터 파편화로 인해 미국 의료 시스템은 연간 300억 달러의 손실을 입습니다. 의료 애플리케이션을 개발하는 개발자에게 HL7 FHIR API 통합은 선택 사항이 아니라, CMS에서 의무화하고 Epic, Cerner 및 모든 주요 EHR 공급업체가 채택한 업계 표준입니다.

현실은 이렇습니다: FHIR 지원 앱을 사용하는 의료기관은 진료 조정 시간을 40% 단축하고 팩스 기반 기록 요청의 85%를 없앱니다. 견고한 FHIR API 통합은 EHR, 환자 포털 및 진료 조정 플랫폼 간의 원활한 데이터 교환을 가능하게 합니다.

이 가이드는 전체 HL7 FHIR API 통합 프로세스를 안내합니다. FHIR 아키텍처, 리소스 유형, 검색 매개변수, OAuth 2.0 인증, SMART on FHIR 통합 및 프로덕션 배포 전략을 배우게 될 것입니다. 이 가이드를 마치면 프로덕션 환경에 바로 적용 가능한 FHIR 통합을 갖추게 될 것입니다.

💡
Apidog는 의료 API 통합을 간소화합니다. FHIR 엔드포인트를 테스트하고, 리소스 스키마를 검증하며, 인증 흐름을 디버그하고, API 사양을 하나의 작업 공간에서 문서화하세요. FHIR 구현 가이드, 모의 응답을 가져오고, 테스트 시나리오를 팀과 공유하세요.
버튼

HL7 FHIR이란 무엇인가요?

FHIR (Fast Healthcare Interoperability Resources)는 의료 정보를 전자적으로 교환하기 위한 표준 프레임워크입니다. Health Level Seven International (HL7)이 개발한 FHIR은 RESTful API, JSON, XML, OAuth 2.0을 포함한 최신 웹 기술을 사용합니다.

FHIR 리소스 유형

FHIR은 140개 이상의 리소스 유형을 정의합니다. 핵심 리소스는 다음과 같습니다:

리소스 목적 일반적인 사용 사례
환자 인구 통계 환자 조회, 등록
의료인 의료기관 정보 디렉터리, 스케줄링
진료 기록 방문/입원 진료 에피소드, 청구
관찰 기록 임상 데이터 활력 징후, 검사 결과, 평가
상태/질병 문제/진단 문제 목록, 진료 계획
약물 요청 처방전 전자 처방, 약물 이력
알레르기 불내성 알레르기 안전 확인, 경고
예방 접종 예방 접종 예방 접종 기록
진단 보고서 검사실/영상 보고서 결과 전달
문서 참조 임상 문서 CCD, 퇴원 요약

FHIR API 아키텍처

FHIR은 RESTful API 구조를 사용합니다:

https://fhir-server.com/fhir/{resourceType}/{id}

FHIR 버전 비교

버전 상태 사용 사례
R4 (4.0.1) 현재 STU 프로덕션 구현
R4B (4.3) 시험 구현 초기 채택자
R5 (5.0.0) 초안 STU 미래 구현
DSTU2 더 이상 사용되지 않음 레거시 시스템 전용

CMS는 환자 접근 및 의료기관 접근 API를 위해 인증된 EHR이 FHIR R4를 지원하도록 요구합니다.

시작하기: FHIR 서버 액세스

단계 1: FHIR 서버 선택

FHIR 서버 배포 옵션:

서버 유형 비용 최적
Azure API for FHIR 관리형 사용량 기반 과금 엔터프라이즈, Azure 사용자
AWS HealthLake 관리형 사용량 기반 과금 AWS 환경
Google Cloud Healthcare API 관리형 사용량 기반 과금 GCP 환경
HAPI FHIR 오픈 소스 자체 호스팅 맞춤형 배포
Epic FHIR 서버 상업용 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

단계 3: FHIR RESTful 작업 이해

FHIR은 표준 HTTP 메서드를 지원합니다:

작업 HTTP 메서드 엔드포인트 설명
읽기 GET /{resourceType}/{id} 특정 리소스 가져오기
검색 GET /{resourceType}?param=value 리소스 검색
생성 POST /{resourceType} 새 리소스 생성
업데이트 PUT /{resourceType}/{id} 리소스 교체
패치 PATCH /{resourceType}/{id} 부분 업데이트
삭제 DELETE /{resourceType}/{id} 리소스 제거
기록 GET /{resourceType}/{id}/_history 리소스 버전

단계 4: 첫 FHIR 호출 실행

연결 테스트:

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

예상 응답:

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

핵심 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();
};

// ID로 환자 읽기
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}`);

환자 리소스 구조

{
  "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"
    }
  ]
}

리소스 검색

이름으로 환자 검색:

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]}`);
});

일반적인 검색 매개변수

리소스 검색 매개변수 예시
환자 name, birthdate, identifier, gender, phone, email ?name=Smith&birthdate=1985-06-15
관찰 기록 patient, code, date, category, status ?patient=123&code=8480-6&date=ge2026-01-01
상태/질병 patient, clinical-status, category, onset-date ?patient=123&clinical-status=active
약물 요청 patient, status, intent, medication ?patient=123&status=active
진료 기록 patient, date, status, class ?patient=123&date=ge2026-01-01
진단 보고서 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

임상 데이터 작업

관찰 기록 생성 (활력 징후)

활력 징후 기록:

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, // 예: 수축기 혈압에 대한 '8480-6'
        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}`);

일반적인 LOINC 코드

코드 표시명 카테고리
8480-6 수축기 혈압 활력 징후
8462-4 이완기 혈압 활력 징후
8867-4 심박수 활력 징후
8310-5 체온 활력 징후
8302-2 신장 활력 징후
29463-7 체중 활력 징후
8871-5 호흡수 활력 징후
2339-0 포도당 [질량/부피] 검사실
4548-4 헤모글로빈 A1c 검사실
2093-3 콜레스테롤 [질량/부피] 검사실

상태 기록 생성 (문제 목록 항목)

문제 목록에 진단 추가:

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

일반적인 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}`);
});

검사 결과 검색

진단 보고서 및 관찰 기록 가져오기:

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

// 특정 검사 (예: HbA1c) 가져오기
const getLabValue = async (patientId, loincCode) => {
  const params = new URLSearchParams({
    patient: patientId,
    code: loincCode
  });

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

// 사용법 - HbA1c 결과 가져오기
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}`);
});

OAuth 2.0 및 SMART on FHIR

FHIR 인증 이해

FHIR 서버는 OpenID Connect와 함께 OAuth 2.0을 사용합니다:

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   클라이언트  │───▶│   인증      │───▶│   FHIR      │
│   (앱)      │    │   서버      │    │ 서버        │
└─────────────┘    └─────────────┘    └─────────────┘
     │                    │                    │
     │  1. 인증 요청      │                    │
     │───────────────────▶│                    │
     │                    │                    │
     │  2. 사용자 로그인    │                    │
     │◀───────────────────│                    │
     │                    │                    │
     │  3. 인증 코드      │                    │
     │───────────────────▶│                    │
     │                    │                    │
     │  4. 토큰 요청      │                    │
     │───────────────────▶│                    │
     │                    │  5. 토큰 + ID      │
     │◀───────────────────│                    │
     │                    │                    │
     │  6. API 요청       │                    │
     │────────────────────────────────────────▶│
     │                    │                    │
     │  7. FHIR 데이터    │                    │
     │◀────────────────────────────────────────│

SMART on FHIR 앱 실행

SMART 앱 실행 구현:

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 저장
    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'
  ]
});

// 사용자를 인증 URL로 리디렉션
const state = crypto.randomBytes(16).toString('hex');
const authUrl = smartClient.buildAuthUrl(state);
console.log(`Redirect to: ${authUrl}`);

필수 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);

배치 및 트랜잭션 작업

배치 요청

여러 독립적인 요청 실행:

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

트랜잭션 요청

여러 요청을 하나의 원자적 단위로 실행:

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' } // 첫 번째 리소스에 대한 참조
    }
  }
]);

구독 및 웹훅

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

FHIR 웹훅 처리

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

일반적인 문제 해결

문제: 401 권한 없음

증상: "권한 없음" 또는 "유효하지 않은 토큰" 오류가 발생합니다.

해결책:

  1. 토큰이 만료되지 않았는지 확인
  2. 토큰 스코프에 요청된 리소스가 포함되어 있는지 확인
  3. Authorization: Bearer {token} 헤더가 존재하는지 확인
  4. FHIR 서버 URL이 토큰 대상과 일치하는지 확인

문제: 403 금지됨

증상: 토큰은 유효하지만 액세스가 거부되었습니다.

해결책:

  1. 사용자에게 요청된 리소스에 대한 권한이 있는지 확인
  2. 환자 컨텍스트가 일치하는지 확인 (환자 스코프 토큰의 경우)
  3. SMART 스코프에 요청된 작업이 포함되어 있는지 확인
  4. 리소스 수준 액세스 제어 확인

문제: 404 찾을 수 없음

증상: 리소스가 존재하지 않거나 엔드포인트가 잘못되었습니다.

해결책:

  1. 리소스 ID가 올바른지 확인
  2. FHIR 기본 URL이 올바른지 확인
  3. 리소스 유형이 서버에서 지원되는지 확인
  4. 버전별 엔드포인트 확인 (R4 vs R4B)

문제: 422 처리할 수 없는 엔터티

증상: 생성/업데이트 시 유효성 검사 오류가 발생합니다.

해결책:

// 유효성 검사 오류 파싱
const error = await response.json();
error.issue?.forEach(issue => {
  console.log(`Severity: ${issue.severity}`);
  console.log(`Location: ${issue.expression?.join('.')}`);
  console.log(`Message: ${issue.details?.text}`);
});

일반적인 원인:

프로덕션 배포 체크리스트

배포 전에:

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

실제 사용 사례

환자 포털 통합

한 의료 시스템이 환자 포털을 구축했습니다:

주요 구현 사항:

임상 의사 결정 지원

한 진료 관리 플랫폼이 CDS를 추가했습니다:

주요 구현 사항:

인구 건강 분석

한 보험사가 인구 건강 대시보드를 구축했습니다:

주요 구현 사항:

결론

HL7 FHIR은 현대 의료 상호운용성의 기반을 제공합니다. 주요 요점:

버튼

자주 묻는 질문 (FAQ)

HL7 FHIR은 어디에 사용되나요?

FHIR은 EHR, 환자 포털, 모바일 앱 및 기타 건강 IT 시스템 간의 의료 데이터 표준화된 교환을 가능하게 합니다. 일반적인 사용 사례에는 환자 액세스 앱, 임상 의사 결정 지원, 인구 건강 및 진료 조정이 포함됩니다.

FHIR을 시작하려면 어떻게 해야 하나요?

공개 FHIR 서버(HAPI FHIR 테스트 서버 등)에 액세스하거나 클라우드 FHIR 서비스(Azure API for FHIR, AWS HealthLake)를 설정하는 것부터 시작하세요. 리소스를 읽고 검색 매개변수를 사용하는 연습을 하세요.

HL7 v2와 FHIR의 차이점은 무엇인가요?

HL7 v2는 이벤트 기반 데이터 교환을 위해 파이프 구분 메시지(ADT, ORM, ORU)를 사용합니다. FHIR은 리소스 기반 액세스를 위해 JSON/XML과 RESTful API를 사용합니다. FHIR은 구현하기 쉽고 최신 웹/모바일 앱에 더 적합합니다.

FHIR은 HIPAA를 준수하나요?

FHIR 자체는 데이터 형식 표준입니다. HIPAA 준수는 구현에 따라 달라집니다: 암호화, 인증, 액세스 제어 및 감사 로깅. 안전한 액세스를 위해 SMART on FHIR과 함께 OAuth 2.0을 사용하세요.

SMART 스코프는 무엇인가요?

SMART 스코프는 FHIR 리소스(예: patient/Observation.read, user/*.read)에 대한 세분화된 액세스 권한을 정의합니다. 앱에 필요한 스코프만 요청하세요.

FHIR에서 리소스를 어떻게 검색하나요?

쿼리 매개변수와 함께 GET 요청을 사용하세요: /Patient?name=Smith&birthdate=ge1980-01-01. FHIR은 수정자(:exact, :contains) 및 접두사(gt, lt, ge, le)를 지원합니다.

Bulk FHIR이란 무엇인가요?

Bulk FHIR($export)은 NDJSON 형식으로 대규모 데이터 세트의 비동기 내보내기를 가능하게 합니다. 인구 건강, 분석 및 데이터 웨어하우징에 사용됩니다.

FHIR 버전 관리는 어떻게 하나요?

특정 FHIR 버전(R4 권장)을 대상으로 하고 버전별 엔드포인트를 사용하세요. 지원되는 버전 및 리소스는 CapabilityStatement를 확인하세요.

FHIR을 사용자 정의 필드로 확장할 수 있나요?

네, FHIR 확장을 사용하여 사용자 정의 데이터 요소를 추가할 수 있습니다. 구현 가이드에 확장을 정의하고, 광범위하게 공유하는 경우 HL7에 등록하세요.

FHIR 개발에 도움이 되는 도구는 무엇인가요?

인기 있는 도구로는 HAPI FHIR(오픈 소스 서버), FHIR 유효성 검사기, Postman 컬렉션, 그리고 API 테스트 및 문서화를 위한 Apidog가 있습니다.

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요