AnpherosAnpheros PlatformDevelopers

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

Як створити медичний API за допомогою TypeScript

Посібник: API Express на TypeScript, який створює записи пацієнтів, зберігає показники артеріального тиску як спостереження FHIR R4, читає їх назад, відображає помилки та перевіряє вебхуки — без власної медичної бази даних.

Цей посібник створює бекенд API програми для відстеження артеріального тиску на TypeScript з Express: реєстрація створює медичний запис користувача, показники зберігаються як кодовані спостереження 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. Усі клінічні дані проходять через 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, щоб запис завжди можна було знайти з вашого боку:

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 потрапляють до походження показника: лікар, інший додаток або модель ШІ зможуть пізніше визначити, що людина виміряла це вдома за допомогою вашого додатка.

Крок 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 повертає дані у форматі, який потрібен вашому додатку. Збережені дані залишаються стандартними. Оскільки ідентифікатор користувача перевіряється за допомогою currentUser, один користувач ніколи не зможе запитати ідентифікатор пацієнта іншого користувача.

Крок 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: Отримуйте сповіщення про нові дані за допомогою вебхука

Показники також можуть надходити з інших джерел: клініка, що записує до того самого запису, лабораторний звіт, інший додаток, до якого підключилася особа. Зареєструйте кінцеву точку один раз:

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 на ваші справжні облікові дані перед тим, як будь-що покине ваш комп'ютер.

Подальші кроки

Часті запитання

Чи потрібна мені власна база даних для медичних даних?

Ні, не для самих медичних даних. Ваша база даних зберігає користувачів, сесії та налаштування продукту, а також ідентифікатор пацієнта для кожного користувача. Спостереження, стани, ліки, документи, їхня історія та хто їх написав, зберігаються у FHIR-записі.

Чи може мобільний додаток викликати Anpheros безпосередньо?

З ключем API — ні: ключ отримує доступ до кожного пацієнта вашого проєкту і має залишатися на вашому сервері. Мобільний додаток може викликати Anpheros безпосередньо з OAuth-токеном, який користувач надав для свого власного запису, як описано в Як створити медичний додаток за допомогою Flutter.

З яким фреймворком це працює?

З будь-яким. 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