Die Wahl des API-Stils ist eine der ersten Entscheidungen, die du triffst – und eine der langlebigsten. REST, RPC, GraphQL oder ein Hybrid: Jeder Ansatz hat Trade-offs, und keiner ist objektiv besser. Entscheidend ist, dass du bewusst wählst und dann konsistent bleibst.
Dieser Artikel hilft dir, den passenden Stil für deinen Kontext zu wählen und Grundprinzipien zu definieren, die für alle Endpoints gelten. Am Ende hast du einen Stil-ADR (Architecture Decision Record), der deine Wahl dokumentiert und als Referenz für das Team dient.
Zielbild
Nach diesem Artikel kannst du:
- Die Vor- und Nachteile von REST, RPC und GraphQL einordnen
- Einen API-Stil für dein Projekt begründet wählen
- Konsistenzregeln definieren, die für alle Endpoints gelten
- Das Prinzip Least Surprise auf dein API-Design anwenden
- Idempotenz und Backward Compatibility als Designziele verankern
Kernfragen
Bevor du weiterliest, versuche diese Fragen für dein Projekt zu beantworten:
- Welcher Stil passt zu unseren Use Cases? CRUD-lastig, aktionsorientiert, flexible Queries?
- Welche Regeln gelten überall? Naming, Fehlerformat, Datumsformat, Pagination?
- Wie garantieren wir Idempotenz? Welche Operationen sind sicher wiederholbar?
- Wie schützen wir Backward Compatibility? Was ist ein Breaking Change, was nicht?
- Wer definiert und enforced die Standards? Dokumentation, Linting, Reviews?
API-Stile im Vergleich
REST (Representational State Transfer)
REST modelliert APIs als Ressourcen, die über HTTP-Methoden manipuliert werden. Es ist der De-facto-Standard für öffentliche APIs.
Stärken:
- Weit verbreitet, gut verstanden
- HTTP-Caching out of the box
- Klare Semantik durch HTTP-Methoden
- Gute Tooling-Unterstützung (OpenAPI, Postman, etc.)
Schwächen:
- Over-fetching und Under-fetching bei komplexen Datenstrukturen
- Aktionen, die nicht in CRUD passen, werden unelegant
- Mehrere Roundtrips für zusammenhängende Daten
Passt gut für: CRUD-lastige APIs, öffentliche APIs, APIs mit Caching-Anforderungen.
RPC (Remote Procedure Call)
RPC modelliert APIs als Funktionsaufrufe. Endpoints repräsentieren Aktionen, nicht Ressourcen.
Stärken:
- Natürlich für aktionsorientierte Use Cases
- Klare Benennung:
POST /orders/{id}/cancelstattPATCH /orders/{id} - Einfacher bei komplexen Operationen
Schwächen:
- Kein standardisiertes Caching
- Weniger Konsistenz, wenn nicht strikt enforced
- HTTP-Semantik wird oft ignoriert
Passt gut für: Interne Services, aktionsorientierte APIs, Workflow-getriebene Anwendungen.
GraphQL
GraphQL ist eine Query-Sprache, bei der Clients genau die Daten anfordern, die sie brauchen.
Stärken:
- Keine Over-/Under-fetching-Probleme
- Typischerweise ein zentraler Endpoint
- Starke Typisierung und Introspection
- Gut für Frontend-getriebene Entwicklung
Schwächen:
- Komplexeres Caching (klassisches HTTP-Caching selten von Haus aus)
- Höhere Einstiegshürde für Clients
- N+1-Query-Probleme auf Serverseite
- Schwieriger zu rate-limiten
Passt gut für: Datenintensive Frontends, Mobile Apps mit variablen Bandbreiten, APIs mit stark vernetzten Daten.
Hybrid
In der Praxis kombinieren viele APIs Stile: REST für CRUD, RPC-artige Endpoints für Aktionen, vielleicht GraphQL für flexible Queries.
Regel: Hybrid ist legitim, aber dokumentiere klar, wann welcher Stil gilt. Inkonsistenz ohne Begründung ist das Problem, nicht der Mix selbst.
Konsistenzregeln definieren
Egal welchen Stil du wählst – Konsistenz ist wichtiger als die Wahl selbst. Definiere Regeln, die für alle Endpoints gelten.
Naming Conventions
| Aspekt | Regel | Beispiel |
|---|---|---|
| URL-Pfade | Kebab-case, Plural für Collections | /order-items, nicht /orderItems |
| Query-Parameter | snake_case | ?page_size=20 |
| JSON-Felder | snake_case | created_at, nicht createdAt |
| Ressourcen-IDs | Nicht erratbar, stabil | UUIDs oder Hashids, keine sequentiellen IDs |
Anti-Pattern: Naming je nach Endpoint unterschiedlich. Das erzeugt kognitive Last bei Konsumenten.
Fehlerformat
Definiere ein einheitliches Fehlerformat für alle Endpoints. Details folgen in Teil 8, aber die Grundstruktur sollte früh feststehen:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{
"field": "email",
"reason": "Invalid email format"
}
],
"request_id": "req_abc123"
}
}
Regel: Jeder 4xx/5xx-Response folgt diesem Format. Keine Ausnahmen.
Datumsformat
Verwende ISO 8601 mit Zeitzone, immer:
{
"created_at": "2026-01-24T14:30:00Z",
"updated_at": "2026-01-24T15:45:00+01:00"
}
Anti-Pattern: Unix-Timestamps, lokale Formate oder Datumsstrings ohne Zeitzone.
Pagination
Wähle ein Pagination-Modell und bleib dabei. Details folgen in Teil 7, aber entscheide früh:
- Offset-based:
?offset=20&limit=10– einfach, aber problematisch bei großen Datasets - Cursor-based:
?cursor=abc123&limit=10– stabiler, aber komplexer
Regel: Alle List-Endpoints nutzen dasselbe Pagination-Modell.
Prinzip: Least Surprise
APIs sollten sich so verhalten, wie Entwickler es erwarten. Least Surprise bedeutet:
- HTTP-Methoden korrekt nutzen: GET liest, POST erstellt oder triggert nicht-idempotente Aktionen, PUT ersetzt, PATCH aktualisiert, DELETE löscht.
- Statuscodes korrekt nutzen: 200 für Erfolg, 201 für Created, 204 für No Content, 400 für Client-Fehler, 500 für Server-Fehler.
- Keine versteckten Seiteneffekte: Ein GET darf keine Daten verändern. Ein DELETE löscht nur, was im Pfad steht.
- Konsistente Benennung: Wenn ein Feld
created_atheißt, sollte das überall so sein.
Anti-Pattern: GET-Requests, die Daten verändern (z.B.
GET /users/{id}/activate). Das bricht Caching und Erwartungen.
Idempotenz garantieren
Eine Operation ist idempotent, wenn sie bei mehrfacher Ausführung dasselbe Ergebnis liefert. Idempotenz ist kritisch für Robustheit – Clients müssen Requests bei Timeouts sicher wiederholen können.
Idempotenz nach HTTP-Methode
| Methode | Idempotent? | Anmerkung |
|---|---|---|
| GET | Ja | Liest nur, verändert nichts |
| PUT | Ja | Ersetzt Ressource vollständig |
| DELETE | Ja | Zweites DELETE liefert 404, aber Zustand ist gleich |
| POST | Nein (meist) | Kann idempotent gemacht werden mit Idempotency-Key |
| PATCH | Kommt drauf an | Inkrementelle Updates (+1) sind nicht idempotent |
Idempotency-Keys
Für nicht-idempotente Operationen (z.B. Zahlungen) verwende Idempotency-Keys:
POST /payments
Idempotency-Key: pay_abc123
Content-Type: application/json
{ "amount": 100, "currency": "EUR" }
Der Server speichert das Ergebnis unter diesem Key und gibt bei Wiederholung dasselbe zurück.
Regel: Dokumentiere klar, welche Endpoints Idempotency-Keys unterstützen und wie lange sie gültig sind.
Backward Compatibility schützen
Breaking Changes sind teuer – besonders bei externen Konsumenten. Definiere früh, was ein Breaking Change ist und wie du ihn vermeidest.
Was ist ein Breaking Change?
| Änderung | Breaking? |
|---|---|
| Neues optionales Feld hinzufügen | Nein |
| Pflichtfeld im Request optional machen | Nein (macht Requests toleranter) |
| Request-Feld entfernen (nicht mehr akzeptiert) | Ja |
| Neues Pflichtfeld hinzufügen | Ja |
| Feldtyp ändern (string → number) | Ja |
| Enum-Wert entfernen | Ja |
| Endpoint entfernen | Ja |
| Fehlercode ändern | Ja |
| URL-Struktur ändern | Ja |
Strategien für Kompatibilität
- Additive Changes: Neue Felder sind optional, alte bleiben erhalten.
- Tolerant Reader: Clients ignorieren unbekannte Felder.
- Deprecation vor Removal: Felder/Endpoints erst deprecated, dann nach Frist entfernt.
- Versionierung: Bei unvermeidlichen Breaking Changes neue Version einführen.
Anti-Pattern: Das benutzt eh niemand mehr – ohne Metriken ist das eine gefährliche Annahme.
Regeln & Anti-Patterns
Do
- Wähle einen API-Stil bewusst und dokumentiere die Begründung
- Definiere Konsistenzregeln vor dem ersten Endpoint
- Nutze HTTP-Methoden und Statuscodes korrekt
- Mache idempotente Operationen explizit
- Behandle Backward Compatibility als Designziel, nicht als Nachgedanken
Don't
- Stil je nach Endpoint wechseln ohne klare Regel
- Naming, Fehlerformat oder Pagination pro Endpoint neu erfinden
- GET für Zustandsänderungen missbrauchen
- Breaking Changes ohne Vorwarnung einführen
- Idempotenz ignorieren (Clients sollen halt nicht doppelt senden)
Artefakt: Stil-ADR
Dokumentiere deine Stilentscheidung in einem Architecture Decision Record:
# ADR: API-Stil und Grundprinzipien
## Status
Accepted
## Kontext
Wir bauen eine API für [Zielgruppe/Use Case]. Die API wird von [internen
Teams / Partnern / öffentlichen Entwicklern] konsumiert.
## Entscheidung
Wir verwenden **[REST / RPC / GraphQL / Hybrid]** als API-Stil.
Begründung:
- [Grund 1, z.B. CRUD-lastige Use Cases passen gut zu REST]
- [Grund 2, z.B. HTTP-Caching ist wichtig für Performance]
- [Grund 3, z.B. Breite Tooling-Unterstützung für Partner]
## Konsistenzregeln
| Aspekt | Regel |
|-----------------|----------------------------------------------------------------|
| URL-Pfade | Kebab-case, Plural für Collections |
| Query-Parameter | snake_case |
| JSON-Felder | snake_case |
| Datumsformat | ISO 8601 mit Zeitzone (UTC bevorzugt) |
| Fehlerformat | Einheitliches JSON-Schema (siehe Error-Katalog) |
| Pagination | Cursor-based mit `cursor` und `limit` |
| Idempotenz | POST-Endpoints mit Seiteneffekten unterstützen Idempotency-Key |
## Konsequenzen
- Alle neuen Endpoints folgen diesen Regeln
- Abweichungen erfordern Review und Dokumentation
- Bestehende Endpoints werden bei nächster Gelegenheit migriert
Checkliste
Bevor du zum nächsten Artikel gehst, prüfe:
- [ ] API-Stil ist gewählt und begründet
- [ ] Naming Conventions sind definiert (URLs, Query-Params, JSON-Felder)
- [ ] Fehlerformat ist festgelegt
- [ ] Datumsformat ist festgelegt (ISO 8601)
- [ ] Pagination-Modell ist gewählt
- [ ] HTTP-Methoden und Statuscodes werden korrekt verwendet
- [ ] Idempotenz-Strategie ist dokumentiert
- [ ] Breaking-Change-Definition existiert
- [ ] Stil-ADR ist ausgefüllt
Wie es weitergeht
Im nächsten Teil geht es um Ressourcenmodellierung: Welche Substantive werden zu Ressourcen? Wie stabil müssen IDs sein? Und wie tief darf Nesting gehen?
Alle Teile der Serie: Serie: API-Design