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:

  1. Client-Server: Klare Trennung der Verantwortlichkeiten.
  2. Zustandslosigkeit: Jede Anfrage enthält alle nötigen Informationen. Der Server behält keinen Sitzungszustand – das ist der wichtigste Punkt für Skalierung.
  3. Zwischenspeicherbarkeit: Antworten geben an, ob und wie lange sie zwischengespeichert werden dürfen.
  4. Einheitliche Schnittstelle: Ressourcen werden über Adressen angesprochen, Aktionen über HTTP-Methoden.
  5. Schichten: Zwischenstationen (Proxies, Lastverteiler) sind für den Client unsichtbar.
  6. 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-After mitteilen, damit Clients sich anpassen können.
  • CORS: Nur die tatsächlich benötigten Ursprünge erlauben. * zusammen mit Zugangsdaten ist ein Sicherheitsproblem.
  • Zwischenspeicherung: ETag und Cache-Control nutzen. Bei GET auf 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_case oder camelCase) – 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

  1. Sind die Adressen Substantive ohne Verben?
  2. Ermöglicht jede Operation die erwartete HTTP-Methode mit korrektem Statuscode?
  3. Ist die Schnittstelle zustandslos – enthält jede Anfrage alles Nötige?
  4. Gibt es Grenzen für Seitengröße, Anfragegröße und Laufzeit?
  5. Sind Fehlerantworten einheitlich, hilfreich und ohne Interna?
  6. Sind Authentifizierung, Berechtigung und Ratenbegrenzung umgesetzt?
  7. Ist die Versionierung geklärt und der Abkündigungsprozess definiert?
  8. Gibt es eine maschinenlesbare Beschreibung (OpenAPI) – und ist sie aktuell?
  9. Ist das Verhalten bei wiederholten Anfragen definiert (Idempotenz)?
  10. Sind Zwischenspeicherung und Kopfzeilen für die Fehlerbehandlung gesetzt?

Verwandte Grundlagen: REST, API, Idempotenz, Web Application Firewall.