# Wie man eine Gesundheits-API mit TypeScript erstellt

> Tutorial: Eine Express-API in TypeScript, die Patientenakten erstellt, Blutdruckwerte als FHIR R4 Observations speichert, sie zurückliest, Fehler abbildet und Webhooks verifiziert – ohne eigene medizinische Datenbank.

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

**Dieses Tutorial erstellt das Backend der API einer Blutdruck-Tracking-App in TypeScript mit Express: Die Registrierung erstellt die medizinische Akte des Benutzers, Werte werden als kodierte HL7 FHIR R4 Observations gespeichert, die App liest sie zurück und ein Webhook-Endpunkt erfährt von neuen Daten, ohne eigene medizinische Datenbank.** Ihre API verwaltet das, was spezifisch für Ihr Produkt ist (Benutzer, Sitzungen, Einstellungen). Die medizinischen Daten, ihre Historie, Zustimmung und Herkunft leben in Anpheros, das Sie über das [TypeScript SDK](https://developers.anpheros.com/de/guides/sdks) aufrufen.

## Architektur

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

Ihre Datenbank speichert eine zusätzliche Spalte pro Benutzer: deren Anpheros Patienten-ID. Alles Klinische läuft über die API, daher ist es kodiert, versioniert und lesbar für jedes FHIR-System oder für die anderen Apps des Patienten, denen er zustimmt. [Medical app backend and database](https://developers.anpheros.com/de/guides/medical-app-backend) erklärt die Aufteilung im Detail.

## Bevor Sie beginnen

- Node.js 18 oder neuer.
- Ein Sandbox-Schlüssel (`sk_test_…`) von [platform.anpheros.com/dashboard](https://platform.anpheros.com/dashboard/) (**Sandbox-Schlüssel erhalten**). Bewahren Sie ihn nur auf dem Server auf; er darf niemals die Anwendung erreichen.

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

## Schritt 1: Der Client und Ihr Benutzerspeicher

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

## Schritt 2: Registrierung erstellt den medizinischen Datensatz

Wenn sich jemand registriert, erstellen Sie dessen Patientendatensatz in Anpheros und fügen Sie Ihre eigene Benutzer-ID in `identifiers` ein, damit der Datensatz von Ihrer Seite aus immer gefunden werden kann:

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

Die Antwort von Anpheros ist der neue Patient, mit einer `id`, die zu Ihrem Projekt gehört, und dessen FHIR-Referenz:

```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"}
```

## Schritt 3: Messung speichern

Die App sendet `{systolic, diastolic, measuredAt}`. Ihre API validiert diese und speichert eine Blutdruckmessung, LOINC `85354-9`, mit ihren beiden Komponenten:

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

Was Anpheros speichert und zurückgibt:

```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-…"}
```

Das SDK sendet bei jedem Erstellen eine `Idempotency-Key`, sodass ein erneuter Versuch nach einem Netzwerkfehler die Messung nicht doppelt speichert. `author_type: 'patient'` und `source_system` fließen in die Herkunft der Messung ein: Ein Arzt, eine andere App oder ein KI-Modell kann später erkennen, dass die Person diese Messung zu Hause mit Ihrer App durchgeführt hat.

## Schritt 4: Messwerte zurücklesen

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

Ihre API gibt die Form zurück, die Ihre App wünscht. Die gespeicherten Daten bleiben standardisiert. Da die Benutzer-ID von `currentUser` überprüft wird, kann ein Benutzer niemals die Patienten-ID eines anderen Benutzers abfragen.

## Schritt 5: Fehler

Anpheros-Fehler werden als `AnpherosError` mit `status`, `type`, `message`, `field` und einem `requestId` empfangen. Validierungsfehler werden an die App weitergegeben, die restlichen werden ausgeblendet:

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

Beispielsweise wird eine Messung ohne Datum von Anpheros mit `{"error": {"type": "invalid", "message": "Field required (at effective_at)", "field": "effective_at"}}` abgelehnt. Ratenbegrenzungen (`429`) und Serverfehler werden vom SDK erneut versucht, bevor Sie sie sehen. Protokollieren Sie die `requestId` und zitieren Sie diese, wenn Sie sich an den Support wenden. Die vollständige Liste finden Sie unter [Fehler](https://developers.anpheros.com/de/guides/errors).

## Schritt 6: Benachrichtigungen über neue Daten per Webhook erhalten

Messwerte können auch von anderer Stelle eintreffen: eine Klinik, die in denselben Datensatz schreibt, ein Laborbericht, eine andere App, mit der die Person verbunden ist. Registrieren Sie einen Endpunkt einmalig:

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

Die Antwort enthält eine `secret` (`whsec_…`), die einmal angezeigt wird. Verifizieren Sie jede Zustellung anhand des Rohkörpers, bevor Sie ihr vertrauen:

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

Event-Typen sind `resource.created`, `resource.updated`, `resource.deleted`, `consent.granted`, `consent.revoked`, `document.finalized` und `ping`. Zustellungen werden signiert, wiederholt und können wiederholt werden ([Webhooks](https://developers.anpheros.com/de/guides/webhooks)).

## Ausführen

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

Ersetzen Sie den Platzhalter `x-user-id` durch Ihre echte Authentifizierung, bevor etwas Ihren Computer verlässt.

## Weiterführende Informationen

- **Personen, die bereits einen Datensatz haben.** Ein API-Schlüssel erreicht die Patienten, die Ihr Projekt erstellt hat. Um sich mit dem bestehenden Datensatz einer Person zu verbinden, der von anderen Apps, Kliniken oder Laboren gesammelt wurde, verwenden Sie OAuth und deren Zustimmung: [So implementieren Sie die Zustimmung des Patienten mit SMART on FHIR](https://developers.anpheros.com/de/guides/smart-on-fhir-backend).
- **Strenges FHIR, wenn Sie es benötigen.** `anpheros.fhir.search`, `create`, `transaction`, `everything` und `summary` geben Ihnen die [FHIR R4 API](https://developers.anpheros.com/de/guides/store-medical-records-fhir) mit denselben IDs.
- **KI-Funktionen.** `anpheros.context.build` bereitet einen quellengekennzeichneten Kontext für ein Modell vor: [So erstellen Sie eine KI-Gesundheits-App mit einem FHIR-Backend](https://developers.anpheros.com/de/guides/build-ai-healthcare-app).
- **Produktion.** Produktionsschlüssel (`sk_live_…`) werden an verifizierte Organisationen mit einer unterzeichneten DPA ausgestellt; siehe [Preise](https://developers.anpheros.com/de/guides/pricing) und [Limits und Ratenbegrenzungen](https://developers.anpheros.com/de/guides/limits).

## Häufig gestellte Fragen

### Benötige ich eine eigene Datenbank für medizinische Daten?
Nicht für die medizinischen Daten selbst. Ihre Datenbank speichert Benutzer, Sitzungen und Produkteinstellungen sowie die Patienten-ID für jeden Benutzer. Beobachtungen, Zustände, Medikationen, Dokumente, deren Historie und wer sie verfasst hat, werden im FHIR-Datensatz gespeichert.

### Kann die mobile App Anpheros direkt aufrufen?
Mit einem API-Schlüssel nicht: Der Schlüssel greift auf jeden Patienten Ihres Projekts zu und muss auf Ihrem Server verbleiben. Eine mobile App kann Anpheros direkt mit einem OAuth-Token aufrufen, das die Person für ihre eigenen Daten gewährt hat, wie in [How to build a healthcare app with Flutter](https://developers.anpheros.com/de/guides/healthcare-app-flutter) beschrieben.

### Mit welchem Framework funktioniert das?
Jedem. Das SDK benötigt nur `fetch`, daher läuft es in Express, Fastify, Hono, NestJS, Next.js Route Handlers, Deno oder Bun.

## Verwandte Themen

- [Healthcare API](https://developers.anpheros.com/de/guides/healthcare-api): Worauf Sie bei einer Healthcare API achten sollten und ein Überblick über die Anpheros API
- [Medical data API](https://developers.anpheros.com/de/guides/medical-data-api): Wohin jede Art von medizinischen Daten gelangt
- [Speichern von medizinischen Aufzeichnungen mit FHIR R4](https://developers.anpheros.com/de/guides/store-medical-records-fhir)
- [Webhooks](https://developers.anpheros.com/de/guides/webhooks) und [Fehler](https://developers.anpheros.com/de/guides/errors)
- [Backend und Datenbank für medizinische Apps](https://developers.anpheros.com/de/guides/medical-app-backend)
