AnpherosAnpheros PlatformDevelopers

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

Cum să construiești un API de sănătate cu TypeScript

Tutorial: un API Express în TypeScript care creează fișe de pacienți, stochează măsurători de tensiune arterială ca observații FHIR R4, le citește înapoi, mapează erorile și verifică webhooks — fără o bază de date medicală proprie.

Acest tutorial construiește API-ul backend al unei aplicații de urmărire a tensiunii arteriale în TypeScript cu Express: înregistrarea creează dosarul medical al utilizatorului, citirile sunt stocate ca observații codificate HL7 FHIR R4, aplicația le citește înapoi, iar un endpoint webhook află despre date noi, fără o bază de date medicală proprie. API-ul tău păstrează ceea ce este specific produsului tău (utilizatori, sesiuni, setări). Datele medicale, istoricul lor, consimțământul și proveniența trăiesc în Anpheros, pe care îl apelezi prin SDK-ul TypeScript.

Arhitectură

 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 ──────────────┘

Baza de date stochează o coloană suplimentară per utilizator: ID-ul pacientului Anpheros. Tot ce este clinic trece prin API, deci este codificat, versionat și lizibil de către orice sistem FHIR sau de către alte aplicații ale pacientului la care acesta consimte. Backend și baza de date a aplicației medicale explică detaliat divizarea.

Înainte de a începe

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_…

Pasul 1: clientul și stocarea utilizatorului dumneavoastră

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

Pasul 2: Înregistrarea creează fișa medicală

Când cineva se înregistrează, creați pacientul în Anpheros și plasați propriul ID de utilizator în identifiers, astfel încât fișa să poată fi întotdeauna găsită din partea dumneavoastră:

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

Răspunsul de la Anpheros este noul pacient, cu un id care aparține proiectului dumneavoastră și referința sa 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"}

Pasul 3: stocarea unei măsurători

Aplicația trimite {systolic, diastolic, measuredAt}. API-ul dvs. o validează și stochează o observație a tensiunii arteriale, LOINC 85354-9, cu cele două componente ale sale:

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

Ce stochează și returnează 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-ul trimite un Idempotency-Key cu fiecare creare, astfel încât o reîncercare după o eroare de rețea nu stochează măsurătoarea de două ori. author_type: 'patient' și source_system intră în proveniența măsurătorii: un medic, o altă aplicație sau un model AI pot spune ulterior că persoana a efectuat această măsurătoare acasă cu aplicația dvs.

Pasul 4: Citirea înapoi a înregistrărilor

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-ul dvs. returnează forma dorită de aplicația dvs. Datele stocate rămân standard. Deoarece ID-ul utilizatorului este verificat de currentUser, un utilizator nu poate solicita niciodată ID-ul pacientului altui utilizator.

Pasul 5: erori

Erorile Anpheros sosesc ca AnpherosError cu status, type, message, field și un requestId. Transmiteți erorile de validare aplicației și ascundeți restul:

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

De exemplu, o citire fără dată este refuzată de Anpheros cu {"error": {"type": "invalid", "message": "Field required (at effective_at)", "field": "effective_at"}}. Limitele de rată (429) și erorile de server sunt reîncercate de SDK înainte de a le vedea. Înregistrați requestId și citați-l dacă contactați suportul. Lista completă se găsește în Erori.

Pasul 6: aflați despre date noi cu un webhook

Citirile pot sosi și din alte surse: o clinică care scrie în același fișier, un raport de laborator, o altă aplicație la care persoana s-a conectat. Înregistrați un punct final o singură dată:

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

Răspunsul conține un secret (whsec_…), afișat o singură dată. Verificați fiecare livrare în raport cu corpul brut înainte de a avea încredere în ea:

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

Tipurile de evenimente sunt resource.created, resource.updated, resource.deleted, consent.granted, consent.revoked, document.finalized și ping. Livrările sunt semnate, reîncercate și pot fi reluate (Webhooks).

Rulare

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>'

Înlocuiți placeholder-ul x-user-id cu autentificarea dvs. reală înainte ca orice să părăsească mașina dvs.

Mergând mai departe

Întrebări frecvente

Am nevoie de propria bază de date pentru datele medicale?

Nu pentru datele medicale în sine. Baza dvs. de date păstrează utilizatorii, sesiunile și setările produsului, plus ID-ul pacientului pentru fiecare utilizator. Observațiile, condițiile, medicația, documentele, istoricul acestora și cine le-a scris sunt stocate în registrul FHIR.

Poate aplicația mobilă să apeleze Anpheros direct?

Cu o cheie API, nu: cheia ajunge la fiecare pacient al proiectului dvs. și trebuie să rămână pe serverul dvs. O aplicație mobilă poate apela Anpheros direct cu un token OAuth pe care persoana l-a acordat pentru propriul său registru, așa cum este descris în Cum să construiești o aplicație de sănătate cu Flutter.

Cu ce framework funcționează acest lucru?

Oricare. SDK-ul necesită doar fetch, deci rulează în Express, Fastify, Hono, NestJS, gestionare de rute Next.js, Deno sau Bun.

Conexe

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