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 시스템 또는 환자가 동의한 다른 앱에서 코딩, 버전 관리 및 읽기가 가능합니다. 의료 앱 백엔드 및 데이터베이스에서 자세한 내용을 설명합니다.
시작하기 전에
- Node.js 18 이상.
- platform.anpheros.com/dashboard에서 발급받은 샌드박스 키 (
sk_test_…). 서버에만 보관해야 하며, 앱에는 절대 노출되어서는 안 됩니다.
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 자리 표시자를 실제 인증 정보로 바꾸십시오.
추가 정보
- 이미 기록이 있는 사용자. API 키는 프로젝트에서 생성한 환자에게 도달합니다. 다른 앱, 클리닉 또는 실험실에서 수집한 사용자의 기존 기록에 연결하려면 OAuth와 해당 사용자의 동의를 사용하십시오: SMART on FHIR을 사용하여 환자 동의 구현 방법.
- 필요한 경우 엄격한 FHIR 사용.
anpheros.fhir.search,create,transaction,everything및summary는 동일한 ID를 가진 FHIR R4 API를 제공합니다. - AI 기능.
anpheros.context.build는 모델을 위한 소스 레이블이 지정된 컨텍스트를 준비합니다: FHIR 백엔드로 AI 의료 앱 구축 방법. - 운영 환경. 운영 환경 키(
sk_live_…)는 서명된 DPA가 있는 검증된 조직에 발급됩니다. 가격 및 제한 사항 및 속도 제한을 참조하십시오.
자주 묻는 질문
의료 데이터를 위한 자체 데이터베이스가 필요합니까?
의료 데이터 자체를 위해서는 필요하지 않습니다. 사용자의 데이터베이스에는 사용자, 세션, 제품 설정 및 각 사용자의 환자 ID가 저장됩니다. 관찰, 상태, 약물, 문서, 해당 기록 및 작성자는 FHIR 레코드에 저장됩니다.
모바일 앱이 Anpheros를 직접 호출할 수 있습니까?
API 키로는 불가능합니다. 키는 프로젝트의 모든 환자에게 도달하며 서버에 유지되어야 합니다. 모바일 앱은 How to build a healthcare app with Flutter에서와 같이 개인이 자신의 기록에 대해 부여한 OAuth 토큰으로 Anpheros를 직접 호출할 수 있습니다.
어떤 프레임워크와 함께 작동합니까?
모든 프레임워크와 함께 작동합니다. SDK는 fetch만 필요하므로 Express, Fastify, Hono, NestJS, Next.js 라우트 핸들러, Deno 또는 Bun에서 실행됩니다.
관련 자료
- Healthcare API: 헬스케어 API에서 확인해야 할 사항 및 Anpheros API 개요
- Medical data API: 각 종류의 의료 데이터가 어디로 가는지
- FHIR R4를 사용하여 의료 기록 저장 방법
- Webhooks 및 Errors
- Medical app backend and database
Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card