AnpherosAnpheros PlatformDevelopers

Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card

TypeScript로 의료 API 구축 방법

튜토리얼: 환자 기록을 생성하고 혈압 측정을 FHIR R4 관찰값으로 저장하며 다시 읽고 오류를 매핑하며 웹훅을 확인하는 TypeScript의 Express API — 자체 의료 데이터베이스 없이.

이 튜토리얼에서는 Express를 사용하여 TypeScript로 혈압 추적 앱의 백엔드 API를 구축합니다. 회원가입 시 사용자의 의료 기록이 생성되고, 측정값은 코딩된 HL7 FHIR R4 관찰 항목으로 저장되며, 앱은 이를 다시 읽어오고, 웹훅 엔드포인트는 새로운 데이터를 수신합니다. 자체 의료 데이터베이스는 필요하지 않습니다. API는 제품별 고유 정보(사용자, 세션, 설정)를 유지합니다. 의료 데이터, 기록, 동의 및 출처는 Anpheros에 저장되며, TypeScript SDK를 통해 접근합니다.

아키텍처

 mobile / web app ──► your API (Express, TypeScript) ──► Anpheros (FHIR R4 record per patient)
                       │  users, sessions, settings        │  observations, conditions, documents,
                       │  user id → patient id              │  provenance, consent, audit
                       ◄──────────── webhooks ──────────────┘

데이터베이스에는 사용자당 Anpheros 환자 ID라는 추가 열이 저장됩니다. 모든 임상 데이터는 API를 통해 처리되므로, FHIR 시스템 또는 환자가 동의한 다른 앱에서 코딩, 버전 관리 및 읽기가 가능합니다. 의료 앱 백엔드 및 데이터베이스에서 자세한 내용을 설명합니다.

시작하기 전에

mkdir bp-api && cd bp-api
npm init -y && npm pkg set type=module
npm install express @anpheros/sdk
npm install -D typescript tsx @types/express @types/node
export ANPHEROS_KEY=sk_test_…

1단계: 클라이언트와 사용자 저장소

// server.ts
import express from 'express';
import { Anpheros, AnpherosError, apiKey, verifyWebhookSignature } from '@anpheros/sdk';

const anpheros = new Anpheros({ auth: apiKey(process.env.ANPHEROS_KEY!) });
const app = express();

// Your own user table. In production this is your database; the only medical link is patientId.
type User = { id: string; email: string; patientId: string };
const users = new Map<string, User>();

// Stand-in for your real authentication (session cookie, JWT…): it must set the signed-in user.
function currentUser(req: express.Request): User {
  const user = users.get(req.header('x-user-id') ?? '');
  if (!user) throw new HttpError(401, 'not signed in');
  return user;
}

class HttpError extends Error {
  constructor(readonly status: number, message: string) { super(message); }
}

단계 2: 회원가입 시 의료 기록 생성

누군가 회원가입하면 Anpheros에 해당 환자를 생성하고 identifiers에 사용자 ID를 넣어, 해당 기록을 항상 귀하의 측에서 찾을 수 있도록 합니다.

app.post('/signup', express.json(), async (req, res, next) => {
  try {
    const { email, given, family, birthDate } = req.body;
    const userId = crypto.randomUUID();
    const patient = await anpheros.patients.create(
      { given, family, birth_date: birthDate, identifiers: [{ system: 'https://bp.example/user', value: userId }] },
      { author_type: 'patient' },
    );
    users.set(userId, { id: userId, email, patientId: patient.id });
    res.status(201).json({ userId });
  } catch (e) { next(e); }
});

Anpheros의 응답은 새로운 환자 정보이며, 프로젝트에 속한 id와 FHIR 참조를 포함합니다.

{"id": "27296656-c2ba-4970-8424-1dcd91163e0a", "given": "Ioana", "family": "Marin", "birth_date": "1979-04-12", "gender": "female",
 "identifiers": [{"system": "https://bp.example/user", "value": "u_8f2c"}], "updated_at": "2026-10-12T20:16:51.143Z",
 "fhir": "Patient/27296656-c2ba-4970-8424-1dcd91163e0a"}

3단계: 측정값 저장

앱은 {systolic, diastolic, measuredAt}을 전송합니다. API는 이를 검증하고 LOINC 85354-9의 혈압 측정값 하나와 두 개의 구성 요소를 저장합니다.

app.post('/readings', express.json(), async (req, res, next) => {
  try {
    const user = currentUser(req);
    const { systolic, diastolic, measuredAt } = req.body;
    if (![systolic, diastolic].every((v) => Number.isFinite(v) && v > 20 && v < 300)) {
      throw new HttpError(400, 'systolic and diastolic must be numbers in mmHg');
    }
    const obs = await anpheros.observations.create(user.patientId, {
      code: '85354-9',
      category: 'vital-signs',
      effective_at: measuredAt ?? new Date().toISOString(),
      components: [
        { code: '8480-6', value: systolic, unit: 'mm[Hg]' },
        { code: '8462-4', value: diastolic, unit: 'mm[Hg]' },
      ],
      author_type: 'patient',
    }, { source_system: 'bp-app' });
    res.status(201).json({ id: obs.id, measuredAt: obs.effective_at });
  } catch (e) { next(e); }
});

Anpheros가 저장하고 반환하는 내용:

{"id": "78188270-e65e-49bd-89d0-e776226e9614", "patient": "27296656-…", "code": "85354-9", "system": "http://loinc.org",
 "display": "Blood pressure panel", "category": "vital-signs", "effective_at": "2026-10-10T08:30:00Z",
 "components": [{"code": "8480-6", "system": "http://loinc.org", "value": 138.0, "unit": "mm[Hg]"},
                {"code": "8462-4", "system": "http://loinc.org", "value": 88.0, "unit": "mm[Hg]"}],
 "status": "final", "updated_at": "2026-10-12T20:16:51.153Z", "fhir": "Observation/78188270-…"}

SDK는 생성할 때마다 Idempotency-Key을 전송하므로, 네트워크 오류 후 재시도해도 측정값이 두 번 저장되지 않습니다. author_type: 'patient' 및 source_system은 측정값의 출처 정보에 포함됩니다. 의사, 다른 앱 또는 AI 모델은 나중에 해당 개인이 귀하의 앱으로 집에서 측정했음을 알 수 있습니다.

4단계: 판독값 다시 읽기

app.get('/readings', async (req, res, next) => {
  try {
    const user = currentUser(req);
    const page = await anpheros.observations.list(user.patientId, {
      code: '85354-9',
      from: typeof req.query.from === 'string' ? req.query.from : undefined,
      limit: 50,
    });
    res.json({
      readings: page.data.map((o) => ({
        id: o.id,
        measuredAt: o.effective_at,
        systolic: o.components.find((c) => c.code === '8480-6')?.value,
        diastolic: o.components.find((c) => c.code === '8462-4')?.value,
      })),
      next: page.next_cursor,
    });
  } catch (e) { next(e); }
});

API는 앱이 원하는 형식으로 데이터를 반환합니다. 저장된 데이터는 표준을 유지합니다. 사용자 ID는 currentUser에 의해 확인되므로, 한 사용자는 다른 사용자의 환자 ID를 요청할 수 없습니다.

5단계: 오류

Anpheros 오류는 AnpherosError, status, type, message, field 및 requestId과 함께 제공됩니다. 유효성 검사 오류는 앱으로 전달하고 나머지는 숨깁니다.

app.use((err: unknown, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
  if (err instanceof HttpError) return res.status(err.status).json({ error: err.message });
  if (err instanceof AnpherosError && err.status === 400) {
    return res.status(400).json({ error: err.message, field: err.field });
  }
  console.error(err instanceof AnpherosError ? `anpheros ${err.status} ${err.type} request=${err.requestId}` : err);
  res.status(502).json({ error: 'medical record service unavailable, try again' });
});

예를 들어, 날짜가 없는 측정값은 Anpheros에서 {"error": {"type": "invalid", "message": "Field required (at effective_at)", "field": "effective_at"}} 오류를 반환합니다. 속도 제한(429) 및 서버 오류는 SDK에서 재시도한 후 표시됩니다. requestId을 기록하고 지원팀에 문의할 경우 해당 내용을 인용하십시오. 전체 목록은 오류에서 확인할 수 있습니다.

6단계: 웹훅으로 새 데이터 알림 받기

다른 곳에서 새로운 Readings가 도착할 수도 있습니다. 예를 들어, 같은 레코드에 쓰는 클리닉, 검사 보고서, 사용자가 연결한 다른 앱 등입니다. 엔드포인트를 한 번 등록하세요:

curl -X POST https://platform.anpheros.com/v1/webhooks -H "Authorization: Bearer $ANPHEROS_KEY" \
     -H "Content-Type: application/json" \
     -d '{"url": "https://bp.example/webhooks/anpheros", "events": ["resource.created", "consent.revoked"]}'

응답에는 secret (whsec_…)이 포함되며, 이는 한 번 표시됩니다. 신뢰하기 전에 모든 전달 내용을 원시 본문과 비교하여 확인하세요:

app.post('/webhooks/anpheros', express.raw({ type: 'application/json' }), async (req, res) => {
  const ok = await verifyWebhookSignature({
    secret: process.env.ANPHEROS_WEBHOOK_SECRET!,
    header: req.header('anpheros-signature'),
    body: req.body as Buffer,
  });
  if (!ok) return res.status(400).end();
  const event = JSON.parse((req.body as Buffer).toString('utf8'));
  // Events carry ids and versions, not clinical data: fetch what you need, then update caches or notify.
  console.log(event.type, event.data);
  res.status(204).end();
});

app.listen(3000, () => console.log('bp-api on http://localhost:3000'));

이벤트 유형은 resource.created, resource.updated, resource.deleted, consent.granted, consent.revoked, document.finalized 및 ping입니다. 전달은 서명되고, 재시도되며, 다시 재생될 수 있습니다(웹훅).

실행하기

npx tsx server.ts
curl -X POST localhost:3000/signup -H 'content-type: application/json' \
     -d '{"email":"[email protected]","given":"Ioana","family":"Marin","birthDate":"1979-04-12"}'
# {"userId":"…"}
curl -X POST localhost:3000/readings -H 'content-type: application/json' -H 'x-user-id: <userId>' \
     -d '{"systolic":138,"diastolic":88,"measuredAt":"2026-10-10T08:30:00Z"}'
curl localhost:3000/readings -H 'x-user-id: <userId>'

기기가 데이터를 전송하기 전에 x-user-id 자리 표시자를 실제 인증 정보로 바꾸십시오.

추가 정보

자주 묻는 질문

의료 데이터를 위한 자체 데이터베이스가 필요합니까?

의료 데이터 자체를 위해서는 필요하지 않습니다. 사용자의 데이터베이스에는 사용자, 세션, 제품 설정 및 각 사용자의 환자 ID가 저장됩니다. 관찰, 상태, 약물, 문서, 해당 기록 및 작성자는 FHIR 레코드에 저장됩니다.

모바일 앱이 Anpheros를 직접 호출할 수 있습니까?

API 키로는 불가능합니다. 키는 프로젝트의 모든 환자에게 도달하며 서버에 유지되어야 합니다. 모바일 앱은 How to build a healthcare app with Flutter에서와 같이 개인이 자신의 기록에 대해 부여한 OAuth 토큰으로 Anpheros를 직접 호출할 수 있습니다.

어떤 프레임워크와 함께 작동합니까?

모든 프레임워크와 함께 작동합니다. SDK는 fetch만 필요하므로 Express, Fastify, Hono, NestJS, Next.js 라우트 핸들러, Deno 또는 Bun에서 실행됩니다.

관련 자료

Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card

Product updates by email

New features, guides and the end of the beta. One email a month at most.

Send me occasional Anpheros Platform updates by email (new features, API changes, guides). I'll confirm by email and can unsubscribe in one click from any email. Privacy