Eine API ist die Visitenkarte deines Backends. Sie ist das, womit andere Entwickler – Frontend-Kollegen, Partnerfirmen, manchmal die ganze Welt – jeden Tag arbeiten. Eine gut gestaltete REST-API fühlt sich vorhersehbar an: Man muss sie kaum lesen, weil sie sich so verhält, wie man es erwartet. Eine schlecht gestaltete API kostet jeden Nutzer Stunden und ist später kaum noch zu ändern, ohne bestehende Clients zu zerbrechen. Dieser Artikel zeigt dir die Konventionen, die sich in der Praxis bewährt haben.
Was REST eigentlich bedeutet
REST (Representational State Transfer) ist kein Protokoll und kein Framework, sondern ein Architekturstil, den Roy Fielding im Jahr 2000 in seiner Dissertation beschrieben hat. Der Kerngedanke: Du modellierst deine Anwendung als Sammlung von Ressourcen, die über eindeutige URLs adressierbar sind und mit den Standard-HTTP-Methoden manipuliert werden. Der Server hält keinen Sitzungszustand zwischen den Anfragen – jede Anfrage enthält alles, was zu ihrer Verarbeitung nötig ist (statelessness).
In der Praxis nennen sich viele Schnittstellen „RESTful", erfüllen aber nur einen Teil der Kriterien. Das ist nicht schlimm – wichtiger als akademische Reinheit ist, dass deine API konsistent und nachvollziehbar ist.
Ressourcen statt Aktionen
Der häufigste Anfängerfehler ist, Verben in die URL zu schreiben. Eine REST-URL beschreibt ein Ding (ein Substantiv), nicht eine Tätigkeit. Die Tätigkeit liefert die HTTP-Methode.
- Schlecht:
POST /createUser,GET /getUserById?id=42,POST /deleteUser - Gut:
POST /users,GET /users/42,DELETE /users/42
Nutze für Sammlungen den Plural (/users, /orders, /products) und hänge die ID an, um ein einzelnes Element anzusprechen (/users/42). Verschachtelte Ressourcen bildest du über den Pfad ab: /users/42/orders liefert alle Bestellungen von Nutzer 42. Übertreibe es aber nicht mit der Verschachtelung – mehr als zwei Ebenen werden schnell unübersichtlich.
Die HTTP-Methoden richtig einsetzen
Jede Methode hat eine klar definierte Semantik. Halte dich daran, dann verhält sich deine API erwartbar – und Caches, Proxies und Browser tun das Richtige.
- GET liest Daten und verändert nichts (sicher und idempotent). Mehrfaches Aufrufen hat keinen Nebeneffekt.
- POST erstellt eine neue Ressource oder stößt eine Verarbeitung an. Nicht idempotent: Zweimal abgeschickt entstehen oft zwei Datensätze.
- PUT ersetzt eine Ressource vollständig und ist idempotent. Du schickst das komplette Objekt.
- PATCH ändert nur einzelne Felder einer Ressource (Teilaktualisierung).
- DELETE entfernt eine Ressource und ist idempotent.
Der Unterschied zwischen idempotent und nicht-idempotent ist mehr als Theorie: Bei Netzwerkproblemen darf ein Client einen PUT oder DELETE bedenkenlos wiederholen, einen POST hingegen nicht ohne Schutzmechanismus (Stichwort Idempotency-Key).
Statuscodes sprechen lassen
Der HTTP-Statuscode ist die erste Information, die ein Client auswertet. Gib niemals einen Erfolg mit Status 200 zurück und packe die echte Fehlermeldung in den Body – damit zwingst du jeden Client, deinen Body zu parsen, statt einfach den Code zu lesen. Die wichtigsten Codes:
- 200 OK – Anfrage erfolgreich (GET, PATCH, PUT mit Antwortkörper).
- 201 Created – Ressource erfolgreich angelegt; idealerweise mit
Location-Header auf die neue URL. - 204 No Content – Erfolg ohne Antwortkörper (häufig nach DELETE).
- 400 Bad Request – fehlerhafte Eingabe, Validierung fehlgeschlagen.
- 401 Unauthorized – keine oder ungültige Authentifizierung.
- 403 Forbidden – authentifiziert, aber nicht berechtigt.
- 404 Not Found – Ressource existiert nicht.
- 409 Conflict – Konflikt, etwa doppelte E-Mail-Adresse.
- 422 Unprocessable Entity – semantisch fehlerhaft, oft für Validierungsfehler genutzt.
- 500 Internal Server Error – unerwarteter Serverfehler.
Eine ausführliche Behandlung der Fehlerklassen und ihrer Bedeutung findest du in unserem Artikel zu HTTP-Statuscodes und API-Fehlerbehandlung.
Ein einheitliches Fehlerformat
Clients brauchen mehr als einen Statuscode, um sinnvoll auf Fehler zu reagieren. Definiere ein konsistentes JSON-Format für alle Fehler. Bewährt hat sich der Standard RFC 9457 (Problem Details for HTTP APIs), der ein vorhersehbares Schema vorgibt:
- ein maschinenlesbarer Fehlertyp oder -code,
- eine kurze, menschenlesbare Nachricht,
- optional ein Feldverweis bei Validierungsfehlern,
- idealerweise eine Trace- oder Request-ID zum Wiederfinden im Log.
Wichtig: Gib in Produktion niemals interne Stacktraces, SQL-Fehler oder Dateipfade an den Client zurück – das ist ein Sicherheitsrisiko. Logge die Details serverseitig und liefere dem Client nur eine generische Meldung samt Request-ID.
Paginierung, Filterung und Sortierung
Eine Sammlung kann tausende Einträge enthalten. Gib niemals alles auf einmal zurück. Es gibt zwei gängige Ansätze:
- Offset-Paginierung über Query-Parameter wie
?page=2&limit=20oder?offset=40&limit=20. Einfach umzusetzen, aber bei großen Datenmengen und sich ändernden Daten anfällig für Verschiebungen. - Cursor-Paginierung über einen Zeiger auf den letzten gelesenen Datensatz (
?after=eyJpZCI6MTAwfQ). Stabiler und performanter bei großen, lebendigen Datenmengen.
Filterung und Sortierung gehören ebenfalls in Query-Parameter: GET /products?category=audio&sort=-price liefert Audio-Produkte, absteigend nach Preis sortiert. Halte die Parameternamen über deine gesamte API hinweg einheitlich.
Versionierung von Anfang an mitdenken
Sobald ein einziger Client deine API nutzt, kannst du sie nicht mehr beliebig ändern, ohne etwas kaputtzumachen. Plane Versionierung daher früh ein. Die verbreitetste Variante ist die Version im Pfad: /v1/users. Sie ist offensichtlich, einfach zu cachen und für jeden im Browser nachvollziehbar. Alternativen sind die Version im Accept-Header (sauberer, aber weniger sichtbar) oder ein Custom-Header.
Die wichtigste Regel: Bestehende Felder nicht entfernen oder in der Bedeutung ändern. Neue, optionale Felder hinzuzufügen ist abwärtskompatibel; ein Pflichtfeld zu entfernen ist es nicht. Behandle einen Breaking Change immer als neue Version.
Konsistenz schlägt Cleverness
Entscheide dich für eine Schreibweise und ziehe sie konsequent durch. In JSON-Bodies ist camelCase oder snake_case beides legitim – aber mische sie nicht. Datums- und Zeitangaben immer im ISO-8601-Format mit Zeitzone (2025-03-18T14:30:00Z). Bei der Generierung sicherer Tokens oder API-Schlüssel hilft dir der Passwort-Generator aus unserem Webtools-Bereich, und für die Validierung von Strukturen lohnt ein Blick auf unsere JSON-Grundlagen.
Dokumentation und Tests gehören dazu
Eine API ohne Dokumentation ist nur eine halbe API. Der De-facto-Standard ist die OpenAPI-Spezifikation (früher Swagger): eine maschinenlesbare Beschreibung aller Endpunkte, Parameter und Schemata. Aus ihr lassen sich interaktive Dokumentationen, Client-SDKs und Mock-Server generieren. Pflege die Spezifikation als Teil deines Codes, nicht nebenbei.
Zum Testen deiner Endpunkte während der Entwicklung leisten Tools wie Postman, Insomnia oder das schlanke, offline-fähige Bruno gute Dienste.
Häufige Fragen
Soll ich PUT oder PATCH für Updates nehmen?
PUT, wenn der Client das vollständige Objekt schickt und es vollständig ersetzt werden soll. PATCH, wenn nur einzelne Felder geändert werden. In der Praxis ist PATCH oft komfortabler für Clients, weil sie nicht das ganze Objekt kennen müssen.
Brauche ich HATEOAS?
HATEOAS (Hypermedia as the Engine of Application State) bedeutet, dass Antworten Links zu verwandten Aktionen enthalten. Es ist die strengste REST-Stufe, in der Praxis aber selten vollständig umgesetzt, weil der Aufwand für die meisten Projekte den Nutzen übersteigt. Für öffentliche, langlebige APIs kann es sich lohnen.
REST oder GraphQL?
REST glänzt bei klar abgegrenzten Ressourcen, gutem Caching und Einfachheit. GraphQL ist stark, wenn Clients sehr unterschiedliche Datenausschnitte brauchen und du Over- und Underfetching vermeiden willst. Beide haben ihre Berechtigung; für viele klassische CRUD-Anwendungen ist eine saubere REST-API die pragmatischere Wahl.
Wie gehe ich mit Massenoperationen um?
Für das Anlegen oder Ändern vieler Datensätze gibt es keinen REST-Standard. Üblich ist ein eigener Endpunkt, der ein Array entgegennimmt, oder ein asynchroner Batch-Job, dessen Status man über eine separate Status-Ressource abfragt. Wichtig ist, dem Client mitzuteilen, welche Elemente erfolgreich waren und welche nicht.