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

> Tutorial: un API Express în TypeScript care creează înregistrări de pacienți, stochează citiri ale tensiunii arteriale ca observații FHIR R4, le citește înapoi, mapează erori și verifică webhooks — fără o bază de date medicală proprie.

Source: https://developers.anpheros.com/ro/guides/healthcare-api-typescript

**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](https://developers.anpheros.com/ro/guides/sdks).

## 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](https://developers.anpheros.com/ro/guides/medical-app-backend) explică detaliat divizarea.

## Înainte de a începe

- Node.js 18 sau o versiune mai nouă.
- O cheie sandbox (`sk_test_…`) de pe [platform.anpheros.com/dashboard](https://platform.anpheros.com/dashboard/) (**Obține o cheie sandbox**). Păstreaz-o doar pe server; nu trebuie să ajungă niciodată în aplicație.

```bash
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ă

```ts
// 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ă:

```ts
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:

```json
{"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:

```ts
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:

```json
{"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

```ts
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:

```ts
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](https://developers.anpheros.com/ro/guides/errors).

## 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ă:

```bash
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:

```ts
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](https://developers.anpheros.com/ro/guides/webhooks)).

## Rulare

```bash
npx tsx server.ts
curl -X POST localhost:3000/signup -H 'content-type: application/json' \
     -d '{"email":"ioana@example.com","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

- **Persoane care au deja un dosar.** O cheie API ajunge la pacienții creați de proiectul dumneavoastră. Pentru a vă conecta la dosarul existent al unei persoane, colectat de alte aplicații, clinici sau laboratoare, utilizați OAuth și consimțământul acestora: [Cum să implementați consimțământul pacientului cu SMART on FHIR](https://developers.anpheros.com/ro/guides/smart-on-fhir-backend).
- **FHIR strict atunci când aveți nevoie de el.** `anpheros.fhir.search`, `create`, `transaction`, `everything` și `summary` vă oferă [API-ul FHIR R4](https://developers.anpheros.com/ro/guides/store-medical-records-fhir) cu aceleași ID-uri.
- **Funcționalități AI.** `anpheros.context.build` pregătește un context etichetat sursă pentru un model: [Cum să construiți o aplicație de sănătate AI cu un backend FHIR](https://developers.anpheros.com/ro/guides/build-ai-healthcare-app).
- **Producție.** Cheile de producție (`sk_live_…`) sunt emise organizațiilor verificate cu un DPA semnat; consultați [Prețuri](https://developers.anpheros.com/ro/guides/pricing) și [Limite și limite de rată](https://developers.anpheros.com/ro/guides/limits).

## Î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](https://developers.anpheros.com/ro/guides/healthcare-app-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

- [API pentru Sănătate](https://developers.anpheros.com/ro/guides/healthcare-api): ce să căutați într-un API pentru sănătate și o prezentare generală a API-ului Anpheros
- [API pentru date medicale](https://developers.anpheros.com/ro/guides/medical-data-api): unde ajunge fiecare tip de date medicale
- [Cum să stocați înregistrări medicale utilizând FHIR R4](https://developers.anpheros.com/ro/guides/store-medical-records-fhir)
- [Webhooks](https://developers.anpheros.com/ro/guides/webhooks) și [Erori](https://developers.anpheros.com/ro/guides/errors)
- [Backend și bază de date pentru aplicații medicale](https://developers.anpheros.com/ro/guides/medical-app-backend)
