HTTP ist mehr als ein Transportprotokoll – es ist ein semantisches Framework. Methoden haben Bedeutung, Statuscodes kommunizieren Ergebnisse, und Header steuern Verhalten. Wer diese Semantik ignoriert, baut APIs, die Clients verwirren, Caching brechen und Debugging erschweren.
Dieser Artikel hilft dir, HTTP-Methoden korrekt einzusetzen und Statuscodes konsistent zu verwenden. Am Ende hast du eine Methoden/Statuscode-Matrix, die als Referenz für dein Team dient.
Zielbild
Nach diesem Artikel kannst du:
- HTTP-Methoden entsprechend ihrer Semantik einsetzen
- Den Unterschied zwischen POST, PUT und PATCH klar begründen
- Statuscodes korrekt und konsistent verwenden
- Async-Operationen, Konflikte und Rate Limits richtig kommunizieren
- Edge Cases wie Partial Success und Conditional Requests handhaben
Kernfragen
Bevor du weiterliest, versuche diese Fragen für dein Projekt zu beantworten:
- Wann POST, wann PUT, wann PATCH? Kannst du den Unterschied erklären?
- Welche Statuscodes nutzt ihr? Nur 200, 400, 500 – oder differenzierter?
- Wie kommuniziert ihr Async-Operationen? Polling, Webhooks, 202?
- Wie geht ihr mit Konflikten um? Optimistic Locking, Last-Write-Wins?
- Wie signalisiert ihr Rate Limits? 429 mit Retry-After?
HTTP-Methoden korrekt einsetzen
GET – Lesen ohne Seiteneffekte
GET ruft Daten ab, ohne den Serverzustand zu ändern. GET-Requests sind:
- Safe: Keine Seiteneffekte, nur Lesen
- Idempotent: Mehrfaches Aufrufen hat keine zusätzlichen Seiteneffekte
- Cacheable: Responses können gecacht werden
GET /orders/order_abc123
Regel: GET darf niemals Daten verändern. Auch nicht nur ein kleines Logging oder Zähler hochsetzen.
Anti-Pattern: GET /orders/order_abc123/markAsRead – das ist ein
Seiteneffekt und gehört zu POST oder PATCH.
POST – Erstellen oder Aktionen ausführen
POST erstellt neue Ressourcen oder führt Aktionen aus. POST-Requests sind:
- Nicht safe: Ändert Serverzustand
- Nicht idempotent: Zweimal ausführen kann zwei Ressourcen erstellen
- Nicht cacheable (default): Responses werden nur mit expliziten Cache-Headern gecacht
POST /orders
Content-Type: application/json
{ "customer_id": "user_xyz", "items": [] }
Response:
HTTP/1.1 201 Created
Location: /orders/order_abc123
{ "id": "order_abc123" }
Regel: POST für alles, was eine neue Ressource erstellt oder eine Aktion ausführt, die nicht idempotent ist.
PUT – Vollständig ersetzen
PUT ersetzt eine Ressource vollständig. Der Client sendet die komplette Repräsentation, der Server überschreibt alles.
- Nicht safe: Ändert Serverzustand
- Idempotent: Zweimal dasselbe senden = dasselbe Ergebnis
- Semantik: Hier ist die neue Version, ersetze die alte komplett
PUT /users/user_abc123
Content-Type: application/json
{
"name": "Max Mustermann",
"email": "max@example.com",
"role": "admin"
}
Regel: Bei PUT muss der Client alle Felder senden. Fehlende Felder gelten als entfernt bzw. überschrieben – nicht ignoriert.
Anti-Pattern: PUT, das nur gesendete Felder aktualisiert. Das ist PATCH-Semantik.
PATCH – Teilweise aktualisieren
PATCH aktualisiert eine Ressource partiell. Nur die gesendeten Felder ändern sich, der Rest bleibt unverändert.
- Nicht safe: Ändert Serverzustand
- Nicht immer idempotent: Kommt auf die Operation an
- Semantik: Ändere nur diese Felder
PATCH /users/user_abc123
Content-Type: application/json
{ "email": "newemail@example.com" }
Regel: PATCH ist der Normalfall für Updates. PUT ist selten nötig, außer du willst explizit alles ersetzen.
DELETE – Entfernen
DELETE entfernt eine Ressource.
- Nicht safe: Ändert Serverzustand
- Idempotent: Zweimal löschen = Ressource ist weg (zweiter Request liefert 204 oder 404)
- Response: 200 mit Body, 204 ohne Body, oder 202 für Async
DELETE /orders/order_abc123
Regel: DELETE löscht nur die adressierte Ressource. Keine Seiteneffekte auf andere Ressourcen.
Frage: Was passiert bei DELETE auf nicht-existente Ressource? Zwei valide Ansätze:
- 404 Not Found: Streng korrekt, Ressource existiert nicht
- 204 No Content: Pragmatisch, Endzustand ist erreicht (Ressource weg)
Wähle einen Ansatz und bleib konsistent.
Methodenübersicht
| Methode | Safe | Idempotent | Cacheable (default) | Typischer Use Case |
|---|---|---|---|---|
| GET | Ja | Ja | Ja | Ressource lesen |
| POST | Nein | Nein | Nein | Ressource erstellen, Aktion ausführen |
| PUT | Nein | Ja | Nein | Ressource vollständig ersetzen |
| PATCH | Nein | Nein* | Nein | Ressource teilweise aktualisieren |
| DELETE | Nein | Ja | Nein | Ressource löschen |
*PATCH kann idempotent sein, ist es aber nicht garantiert.
Statuscodes richtig verwenden
Statuscodes sind keine Dekoration – sie sind maschinenlesbare Semantik. Clients und Middleware (Proxies, Caches, Monitoring) verlassen sich darauf.
2xx – Erfolg
| Code | Name | Wann verwenden |
|---|---|---|
| 200 | OK | Standard-Erfolg mit Response-Body |
| 201 | Created | Ressource wurde erstellt (POST), Location-Header setzen |
| 202 | Accepted | Request akzeptiert, Verarbeitung läuft async |
| 204 | No Content | Erfolg ohne Response-Body (DELETE, manche PATCHs) |
Regel: 201 immer mit Location-Header, der auf die neue Ressource zeigt.
HTTP/1.1 201 Created
Location: /orders/order_abc123
3xx – Redirection
| Code | Name | Wann verwenden |
|---|---|---|
| 301 | Moved Permanently | Ressource dauerhaft umgezogen |
| 302 | Found | Temporäre Umleitung (vermeiden, 307 bevorzugen) |
| 304 | Not Modified | Conditional Request, Ressource unverändert |
| 307 | Temporary Redirect | Temporäre Umleitung, Methode beibehalten |
| 308 | Permanent Redirect | Permanente Umleitung, Methode beibehalten |
Wichtig: 301/302 können die HTTP-Methode ändern (POST → GET). Für APIs besser 307/308 verwenden.
4xx – Client-Fehler
| Code | Name | Wann verwenden |
|---|---|---|
| 400 | Bad Request | Syntaktisch invalider Request (z.B. JSON kaputt, Parsing-Fehler) |
| 401 | Unauthorized | Nicht authentifiziert (kein Token, Token abgelaufen) |
| 403 | Forbidden | Authentifiziert, aber nicht autorisiert |
| 404 | Not Found | Ressource existiert nicht |
| 405 | Method Not Allowed | Methode für diese URL nicht erlaubt |
| 409 | Conflict | Konflikt mit aktuellem Zustand (z.B. Duplikat, Zustandskonflikt) |
| 410 | Gone | Ressource existierte, ist aber dauerhaft entfernt |
| 415 | Unsupported Media Type | Content-Type nicht unterstützt |
| 422 | Unprocessable Entity | Syntaktisch korrekt, aber Validierung schlägt fehl |
| 429 | Too Many Requests | Rate Limit überschritten |
400 vs. 422: Klare Linie:
- 400: Request ist syntaktisch kaputt (kein valides JSON)
- 422: Request ist syntaktisch OK, aber Validierung schlägt fehl (z.B. Pflichtfeld fehlt)
401 vs. 403:
- 401: Wer bist du? – Authentifizierung fehlt oder ungültig
- 403: Ich weiß, wer du bist, aber du darfst das nicht
5xx – Server-Fehler
| Code | Name | Wann verwenden |
|---|---|---|
| 500 | Internal Server Error | Unerwarteter Fehler, Bug, Crash |
| 502 | Bad Gateway | Upstream-Service antwortet nicht korrekt |
| 503 | Service Unavailable | Server überlastet oder in Wartung |
| 504 | Gateway Timeout | Upstream-Service antwortet zu langsam |
Regel: 5xx bedeutet unser Problem. 4xx bedeutet dein Problem. Verwechsle das nie.
Anti-Pattern: 500 für Validierungsfehler. Das ist ein Client-Fehler (4xx).
Spezialfälle behandeln
Async-Operationen (202 Accepted)
Wenn eine Operation länger dauert, blockiere nicht – akzeptiere den Request und verarbeite im Hintergrund.
POST /exports
Content-Type: application/json
{ "format": "csv", "filters": {...} }
Response:
HTTP/1.1 202 Accepted
Location: /exports/export_abc123
{
"id": "export_abc123",
"status": "pending",
"status_url": "/exports/export_abc123"
}
Client pollt status_url oder wartet auf Webhook:
GET /exports/export_abc123
{
"id": "export_abc123",
"status": "completed",
"download_url": "/exports/export_abc123/download"
}
Regel: 202 heißt: Ich hab's verstanden und arbeite dran – nicht fertig.
Preconditions (412 Precondition Failed)
Konflikte entstehen bei konkurrierenden Änderungen. Optimistic Locking mit ETags oder Versionsnummern hilft.
Request mit If-Match (Optimistic Locking):
PATCH /orders/order_abc123
If-Match: "v3"
Content-Type: application/json
{ "status": "shipped" }
Wenn jemand anderes schneller war:
HTTP/1.1 412 Precondition Failed
{
"error": {
"code": "PRECONDITION_FAILED",
"message": "Resource was modified by another request",
"current_version": "v4"
}
}
Regel: Bei 412 sollte der Client die aktuelle Version abrufen und die Änderung erneut versuchen.
Rate Limits (429 Too Many Requests)
Wenn Clients zu viele Requests senden, antworte mit 429 und hilfreichen Headern.
HTTP/1.1 429 Too Many Requests
Retry-After: 60
RateLimit-Policy: "user";q=100;w=60
RateLimit: "user";r=0;t=60
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests, please retry after 60 seconds"
}
}
Wichtige Header:
Retry-After: Sekunden bis zum nächsten erlaubten RequestRateLimit-Policy: Policy-Definition (z.B.q=Quota,w=Window)RateLimit: Aktueller Status (z.B.r=Remaining,t=Reset)
Hinweis: RateLimit und RateLimit-Policy folgen dem aktuellen
IETF-Entwurf draft-ietf-httpapi-ratelimit-headers-10.
Conditional Requests (304 Not Modified)
Clients können fragen, ob sich etwas geändert hat, und Traffic sparen.
Request mit If-None-Match:
GET /orders/order_abc123
If-None-Match: "abc123hash"
Wenn unverändert:
HTTP/1.1 304 Not Modified
ETag: "abc123hash"
Wenn geändert: Normale 200-Response mit neuem ETag.
Partial Success bei Bulk-Operationen
Bei Bulk-Operationen können manche Items erfolgreich sein, andere nicht. Zwei Strategien:
Option 1: All-or-nothing (empfohlen für kritische Operationen)
HTTP/1.1 400 Bad Request
{
"error": {
"code": "BULK_VALIDATION_FAILED",
"message": "Validation failed for some items",
"details": [
{ "index": 2, "field": "email", "reason": "Invalid format" }
]
}
}
Option 2: Multi-Status (207)
HTTP/1.1 207 Multi-Status
{
"results": [
{ "index": 0, "status": 201, "id": "user_001" },
{ "index": 1, "status": 201, "id": "user_002" },
{ "index": 2, "status": 400, "error": { "code": "VALIDATION_ERROR" } }
]
}
Regel: Dokumentiere klar, welche Strategie gilt.
Regeln & Anti-Patterns
Do
- GET nur für Lesen ohne Seiteneffekte
- POST für Erstellen und nicht-idempotente Aktionen
- PATCH für partielle Updates (der Normalfall)
- PUT nur, wenn die Intention vollständiges Ersetzen ist
- 201 mit Location-Header bei erfolgreicher Erstellung
- 4xx für Client-Fehler, 5xx für Server-Fehler
- Retry-After bei 429 und 503
Don't
- GET mit Seiteneffekten (
GET /markAsRead) - PUT für partielle Updates
- 200 für alles (auch Fehler)
- 500 für Validierungsfehler
- 404 wenn du eigentlich 403 meinst (Security through obscurity ist kein Schutz)
- Statuscodes ignorieren und Fehler nur im Body kommunizieren
Artefakt: Methoden/Statuscode-Matrix
Dokumentiere, welche Kombinationen in deiner API vorkommen:
# Methoden/Statuscode-Matrix
## Standard-Responses nach Methode
| Methode | Erfolg | Client-Fehler | Server-Fehler |
|---------|----------|------------------------------|---------------|
| GET | 200, 304 | 400, 401, 403, 404 | 500, 503 |
| POST | 201, 202 | 400, 401, 403, 409, 422 | 500, 503 |
| PUT | 200, 204 | 400, 401, 403, 404, 409, 422 | 500, 503 |
| PATCH | 200, 204 | 400, 401, 403, 404, 409, 422 | 500, 503 |
| DELETE | 200, 204 | 401, 403, 404 | 500, 503 |
## Spezialfälle
| Szenario | Statuscode | Anmerkung |
|-------------------------------|--------------|---------------------------------|
| Ressource erstellt | 201 | Location-Header erforderlich |
| Async-Operation gestartet | 202 | Status-URL im Body |
| Keine Änderung (Conditional) | 304 | Kein Body |
| Validierungsfehler | 400 oder 422 | Fehlerdetails im Body |
| Nicht authentifiziert | 401 | WWW-Authenticate-Header |
| Nicht autorisiert | 403 | — |
| Konflikt (Optimistic Locking) | 409 | Aktuelle Version im Body |
| Rate Limit | 429 | Retry-After-Header erforderlich |
| Server überlastet | 503 | Retry-After-Header empfohlen |
## Header-Konventionen
| Header | Wann | Beispiel |
|--------------|-----------------|------------------------|
| Location | 201 Created | `/orders/order_abc123` |
| ETag | GET, PUT, PATCH | `"v3"` |
| Retry-After | 429, 503 | `60` (Sekunden) |
| X-Request-Id | Alle Responses | `req_abc123` |
Checkliste
Bevor du zum nächsten Artikel gehst, prüfe:
- [ ] HTTP-Methoden werden gemäß ihrer Semantik verwendet
- [ ] GET hat keine Seiteneffekte
- [ ] POST vs. PUT vs. PATCH ist klar abgegrenzt
- [ ] 201 inkludiert Location-Header
- [ ] 4xx vs. 5xx wird korrekt unterschieden
- [ ] 401 vs. 403 ist klar definiert
- [ ] Async-Operationen nutzen 202 mit Status-URL
- [ ] Rate Limits nutzen 429 mit Retry-After
- [ ] Methoden/Statuscode-Matrix ist dokumentiert
Wie es weitergeht
Im nächsten Teil geht es um Request/Response-Design: Was bedeutet null vs. fehlendes Feld? Wie verhinderst du Typwechsel? Und wie gehst du mit Geld, Datum und großen Payloads um?
Alle Teile der Serie: Serie: API-Design