Ein REST-API-Design zu entwerfen heißt, eine Schnittstelle zu gestalten, die andere Entwickler verstehen und langfristig nutzen können. Gute Schnittstellen sind vorhersagbar, fehlertolerant und ändern sich kontrolliert. Dieser Artikel beschreibt die Regeln, die sich in der Praxis bewährt haben.
Was REST ausmacht
REST ist kein Protokoll, sondern ein Stil. Die sechs Einschränkungen nach Roy Fielding sind:
- Client-Server: Klare Trennung der Verantwortlichkeiten.
- Zustandslosigkeit: Jede Anfrage enthält alle nötigen Informationen. Der Server behält keinen Sitzungszustand – das ist der wichtigste Punkt für Skalierung.
- Zwischenspeicherbarkeit: Antworten geben an, ob und wie lange sie zwischengespeichert werden dürfen.
- Einheitliche Schnittstelle: Ressourcen werden über Adressen angesprochen, Aktionen über HTTP-Methoden.
- Schichten: Zwischenstationen (Proxies, Lastverteiler) sind für den Client unsichtbar.
- Code on Demand: Optional – der Server liefert ausführbaren Code (praktisch selten).
Ressourcen statt Aktionen
Die grundlegende Denkweise ist der häufigste Unterschied zwischen einer guten und einer schwerfälligen Schnittstelle. Nicht „Aktionen“ werden modelliert, sondern Dinge – und HTTP-Methoden beschreiben, was mit ihnen geschieht.
# SCHLECHT: Verben in der Adresse
GET /holeServerListe
POST /erstelleServer
POST /loescheServer?id=7
POST /starteServer?id=7
# GUT: Substantive, Aktionen ueber die Methode
GET /server Liste lesen
GET /server/7 Einen lesen
POST /server Neu anlegen
PUT /server/7 Vollstaendig ersetzen (idempotent)
PATCH /server/7 Teilweise aendern
DELETE /server/7 Loeschen
# Die Methode bestimmt, WAS passiert - die Adresse nur, WOMIT.
# Idempotenz bleibt damit erhalten: Zweimal DELETE loescht nicht zweimal
# unterschiedlich viel, zweimal POST erzeugt aber zwei Eintraege.
Verschachtelung und Beziehungen
# Beziehungen als Unterressource - sinnvoll bei starker Abhaengigkeit
GET /kunden/42/bestellungen Bestellungen eines Kunden
POST /kunden/42/bestellungen neue Bestellung fuer den Kunden
GET /kunden/42/bestellungen/7 eine bestimmte Bestellung
# Zu tief verschachteln vermeiden
# SCHLECHT: /kunden/42/bestellungen/7/positionen/3/lieferung
# GUT: /bestellungen/7/positionen und /lieferungen/3
# Filter, Sortierung, Auswahl, Seitengroesse als Abfrageparameter
GET /server?status=aktiv&standort=luebeck
GET /server?sort=-name&limit=50&offset=100
GET /server?felder=name,ip,port # nur bestimmte Felder
GET /protokoll?von=2026-09-01T00:00:00Z&bis=2026-09-26T23:59:59Z
Paginierung
Bei großen Listen ist die Seitengröße zu begrenzen. Zwei Verfahren haben unterschiedliche Eigenschaften:
# Versatz-Paginierung: einfach, aber bei Aenderungen wackelig
GET /server?limit=50&offset=100
# Problem: Wird zwischen zwei Abrufen ein Eintrag eingefuegt,
# verschieben sich die Ergebnisse - doppelte und fehlende Eintraege.
# Cursor-Paginierung: stabil und schnell bei grossen Mengen
GET /server?limit=50&cursor=eyJpZCI6MTAwfQ
# Der Cursor ist ein Verweis auf die letzte gesehene Zeile
# (z. B. base64-kodiertes {"id":100}).
# Bleibt korrekt, auch wenn sich Daten aendern.
# Nachteil: kein direkter Sprung auf Seite 7.
# Antwort mit Informationen zur Weiterfuehrung
{
"daten": [ ... ],
"cursor": "eyJpZCI6MTUwfQ",
"hat_mehr": true
}
Statuscodes richtig verwenden
# Erfolg
200 OK allgemeiner Erfolg (GET, PATCH, PUT)
201 Created nach POST; mit Location-Kopfzeile auf die neue Ressource
202 Accepted angenommen, wird asynchron verarbeitet
204 No Content Erfolg ohne Inhalt (DELETE, PUT ohne Rueckgabe)
# Clientfehler
400 Bad Request fehlerhafte Anfrage (falsche Typen, fehlende Felder)
401 Unauthorized nicht authentifiziert (fehlender Token)
403 Forbidden authentifiziert, aber nicht berechtigt
404 Not Found existiert nicht
405 Method Not Allowed Methode nicht erlaubt; Allow-Kopfzeile setzen
409 Conflict Konflikt mit dem aktuellen Zustand (doppelte ID)
412 Precondition Failed bedingte Anfrage fehlgeschlagen (ETag)
415 Unsupported Media Type falscher Inhaltstyp
422 Unprocessable Entity syntaktisch ok, aber inhaltlich ungueltig
429 Too Many Requests Ratenbegrenzung; Retry-After setzen
# Serverfehler
500 Internal Server Error unerwarteter Fehler - KEINE Details preisgeben!
502 Bad Gateway Zwischenstation erreicht das Ziel nicht
503 Service Unavailable voruebergehend nicht verfuegbar; Retry-After
504 Gateway Timeout Zwischenstation laeuft in ein Zeitlimit
# Der haeufigste Fehler: alles mit 200 zurueckgeben und den Fehler im Rumpf.
# Wer 200 mit {"fehler": "..."} liefert, bricht jede Automatisierung,
# die sich auf Statuscodes verlaesst.
Fehlerantworten gestalten
Eine gute Fehlermeldung sagt, was schiefging, wo und wie man es behebt – ohne Interna zu verraten.
# Gute Fehlerantwort (422)
{
"typ": "https://example.org/fehler/validierung",
"titel": "Eingabe ungueltig",
"status": 422,
"detail": "Zwei Felder konnten nicht verarbeitet werden.",
"fehler": [
{"feld": "port", "meldung": "muss zwischen 1 und 65535 liegen", "wert": 70000},
{"feld": "email", "meldung": "ungueltiges Format"}
],
"trace_id": "a1b2c3d4"
}
# Was NICHT hineingehoert:
# - Stack-Traces (verraet Pfade, Versionen, Bibliotheken)
# - Datenbankabfragen
# - interne Hostnamen
# - Meldungen ueber nicht existierende Benutzer ("Konto existiert nicht"
# verraet, welche Adressen registriert sind)
# Die trace_id in der Antwort erlaubt dem Support, den Vorfall
# im Protokoll zu finden - ohne Interna nach aussen zu geben.
Versionierung
# Variante 1: in der Adresse (haeufigste, am einfachsten)
GET /v1/server
GET /v2/server
# Variante 2: ueber den Inhaltstyp
GET /server
Accept: application/vnd.example.v2+json
# Variante 3: ueber eine Kopfzeile
GET /server
X-API-Version: 2
# Empfehlung: Version in der Adresse. Sie ist sichtbar, testbar
# und funktioniert mit jedem Werkzeug.
# Regeln fuer den Umgang mit Versionen:
# 1. Additive Aenderungen brauchen KEINE neue Version
# (neues Feld in der Antwort, neuer optionaler Parameter)
# 2. Entfernen oder Umbenennen von Feldern braucht eine neue Version
# 3. Beide Versionen eine feste Zeit parallel betreiben (z. B. 6 Monate)
# 4. Abkuendigungstermin im Kopfzeilenfeld Deprecation und Sunet mitteilen
Weitere Praxisfragen
- Authentifizierung: Kurzlebige Token im Kopfzeilenfeld
Authorization: Bearer .... Nie in der Adresse – sie steht in jedem Protokoll. - Idempotenz bei POST: Für Vorgänge, die nicht doppelt ausgeführt werden dürfen, akzeptiert der Server einen
Idempotency-Key. Bei wiederholter Anfrage mit demselben Schlüssel wird die ursprüngliche Antwort zurückgegeben. - Ratenbegrenzung: Kopfzeilen
X-RateLimit-Limit,X-RateLimit-Remaining,Retry-Aftermitteilen, damit Clients sich anpassen können. - CORS: Nur die tatsächlich benötigten Ursprünge erlauben.
*zusammen mit Zugangsdaten ist ein Sicherheitsproblem. - Zwischenspeicherung:
ETagundCache-Controlnutzen. BeiGETauf unveränderliche Daten lohnt eine lange Gültigkeit. - Dokumentation: OpenAPI (früher Swagger) als Standard. Sie ist maschinenlesbar und erzeugt Doku, Testwerkzeuge und Client-Bibliotheken.
- Namensgebung: Durchgehend klein geschrieben mit Bindestrichen oder Unterstrichen für Adressen, und eine Schreibweise für Feldnamen (
snake_caseodercamelCase) – niemals gemischt. - Zeitangaben: Immer ISO 8601 mit Zeitzone (
2026-09-26T21:30:00Z). Nie lokale Zeiten ohne Zeitzone, nie Zeitstempel als Zahl ohne Einheitenangabe.
Prüfliste für ein neues API-Design
- Sind die Adressen Substantive ohne Verben?
- Ermöglicht jede Operation die erwartete HTTP-Methode mit korrektem Statuscode?
- Ist die Schnittstelle zustandslos – enthält jede Anfrage alles Nötige?
- Gibt es Grenzen für Seitengröße, Anfragegröße und Laufzeit?
- Sind Fehlerantworten einheitlich, hilfreich und ohne Interna?
- Sind Authentifizierung, Berechtigung und Ratenbegrenzung umgesetzt?
- Ist die Versionierung geklärt und der Abkündigungsprozess definiert?
- Gibt es eine maschinenlesbare Beschreibung (OpenAPI) – und ist sie aktuell?
- Ist das Verhalten bei wiederholten Anfragen definiert (Idempotenz)?
- Sind Zwischenspeicherung und Kopfzeilen für die Fehlerbehandlung gesetzt?
Verwandte Grundlagen: REST, API, Idempotenz, Web Application Firewall.