für Symfony-Teams
API Reviews ohne systematische Checkliste übersehen konsistent dieselben Kategorien: Security-Aspekte, Fehlerformate, Performance und Testabdeckung werden im Zeitdruck gerne übersprungen. Eine strukturierte Checkliste macht Reviews schneller, vollständiger und wiederholbar – und stellt sicher, dass kein PR mit einer kritischen Lücke gemergt wird.
Inhaltsverzeichnis
- 1. Design-Checkliste: URLs, Verben und Statuscodes
- 2. Security-Checkliste: Auth, Input und Output
- 3. Fehlerbehandlungs-Checkliste: Formate und Vollständigkeit
- 4. Performance-Checkliste: Datenmenge und Queries
- 5. Dokumentations-Checkliste: OpenAPI-Vollständigkeit
- 6. Test-Checkliste: Abdeckung und Szenarien
- 7. Deployment-Checkliste: Backward Compatibility und Migration
- 8. Review-Kategorien im Vergleich: Häufigkeit und Impact
- 9. Zusammenfassung
- 10. FAQ
1. Design-Checkliste: URLs, Verben und Statuscodes
Design-Fehler sind die teuersten, weil sie zu Breaking Changes führen, wenn sie nach dem Launch korrigiert werden. Das Design-Review prüft grundlegende REST-Konformität, bevor über die Implementierung gesprochen wird. Ein Reviewer, der nach Design-Problemen sucht, hat einen anderen Fokus als einer, der Code-Qualität prüft – deshalb sollten beide Aspekte in der Checkliste getrennt gehalten werden.
# API Design Review Checklist — run through every new endpoint
## URL Design
[ ] Ressource als Nomen in Plural: /products, /orders, /users
[ ] Kein Verb in der URL: /getProduct -> /products/{id}
[ ] Maximal 2 Hierarchieebenen: /orders/{id}/items (OK)
[ ] Sub-Ressourcen nur wenn Beziehung wirklich hierarchisch ist
[ ] Konsistente Namensgebung (kebab-case oder camelCase, nie gemischt)
[ ] IDs in Pfad, kein /products?id=42 für einzelne Ressource
## HTTP-Verben
[ ] GET: keine Seiteneffekte, safe und idempotent
[ ] POST: neue Ressource anlegen, gibt 201 mit Location zurück
[ ] PUT: vollständiger Ersatz (alle Felder im Request erforderlich)
[ ] PATCH: partielle Aktualisierung (nur gesendete Felder)
[ ] DELETE: idempotent (zweites Delete gibt 404, kein 500)
[ ] Keine DELETE-Parameter im Body (Query-String oder URL)
## Statuscodes
[ ] 200 nur wenn Ressource zurückgegeben wird
[ ] 201 bei POST (Create) mit Location-Header
[ ] 204 bei DELETE und PATCH ohne Response-Body
[ ] 400 für syntaktisch fehlerhafte Requests (invalid JSON)
[ ] 401 wenn kein Token vorhanden (nicht 403)
[ ] 403 wenn Token gültig aber keine Berechtigung
[ ] 404 wenn Ressource nicht existiert
[ ] 409 bei Konflikten (doppelte Email, Optimistic Lock)
[ ] 422 für Validierungsfehler (fehlende Pflichtfelder, invalid values)
[ ] 429 wenn Rate Limit überschritten
[ ] Kein 200 für Fehler, kein 500 für Client-Fehler
2. Security-Checkliste: Auth, Input und Output
Security-Reviews erfordern einen anderen Mindset als Design- oder Code-Reviews: man muss aktiv nach Lücken suchen, nicht nur prüfen, ob der Code funktioniert. Die häufigsten API-Security-Probleme sind fehlende Autorisierungsprüfungen auf Ressourcenebene (BOLA/IDOR), fehlende Input-Validierung und das versehentliche Exponieren interner Daten in der Response. Ein Reviewer, der systematisch die Security-Checkliste durchgeht, findet diese Klassen von Problemen konsistent.
Besonders wichtig ist die Unterscheidung zwischen Authentifizierung (wer bist du?) und Autorisierung (was darfst du?). Es ist ein häufiger Fehler, nur zu prüfen, ob der Nutzer angemeldet ist, aber nicht, ob er berechtigt ist, die spezifische Ressource zu lesen oder zu schreiben. Ein User mit gültigem JWT darf /orders/42 nur lesen, wenn Bestellung 42 zu seinem Account gehört – ein Symfony Voter oder eine explizite Ownership-Prüfung muss das sicherstellen.
denyAccessUnlessGranted('ORDER_VIEW', $order);
// ...
}
}
// [x] Input validation: Symfony Validator auf DTOs
// src/Dto/CreateProductRequest.php
final readonly class CreateProductRequest
{
public function __construct(
#[NotBlank]
#[Length(max: 255)]
public string $name,
#[NotBlank]
#[Positive]
public float $price,
#[NotBlank]
#[Regex(pattern: '/^[A-Z0-9]{3,20}$/')]
public string $sku,
) {}
}
// [x] Output: Serializer-Gruppen verhindern Daten-Leakage
// Niemals direkt Entity zurückgeben — immer via Serializer-Gruppen
// oder dedizierte Response-DTOs
// [x] CORS: nur erlaubte Origins
// nelmio/cors-bundle: allow_origin: ['https://mironsoft.de']
// [x] Rate Limiting auf allen schreibenden Endpoints (siehe RateLimitSubscriber)
3. Fehlerbehandlungs-Checkliste: Formate und Vollständigkeit
Fehlerresponses sind die am häufigsten vernachlässigte Dimension eines API-Reviews. Es ist üblich, dass Reviewer den Happy Path gründlich prüfen und Fehlerresponses nur kurz überfliegen. Dabei sind Fehlerresponses für die meisten Konsumenten genauso wichtig wie die Erfolgsantworten: Frontend-Teams müssen Fehler sinnvoll anzeigen, Monitoring-Systeme müssen Fehlertypen unterscheiden können und Retry-Logik muss auf Basis des Fehlerstatus entscheiden, ob ein Retry sinnvoll ist.
Die Checkliste für Fehlerbehandlung prüft drei Dimensionen: Erstens das Format (RFC 7807 mit korrektem Content-Type). Zweitens die Vollständigkeit (alle dokumentierten Statuscodes werden tatsächlich ausgelöst). Drittens die Informationsdichte (genug Information für den Client, um zu reagieren, aber keine internen Stack-Traces, Datenbankdetails oder System-Pfade, die Security-Informationen leaken).
4. Performance-Checkliste: Datenmenge und Queries
Performance-Probleme in APIs werden häufig erst im Produktionsbetrieb sichtbar, weil lokale Testdaten zu klein sind, um N+1-Probleme oder Offset-Pagination-Degradation zu zeigen. Eine Performance-Checkliste im Review hilft, diese Probleme proaktiv zu erkennen, bevor sie in die Produktion gelangen. Der Symfony Profiler ist das wichtigste Werkzeug: er zeigt die Anzahl der Datenbankabfragen pro Request und macht N+1-Probleme sofort sichtbar.
# Performance Checklist — run for every List-Endpoint and new Database Query
## Datenbankabfragen
[ ] Symfony Profiler: Anzahl Queries pro Request geprüft?
[ ] Kein N+1: Beziehungen via JOIN oder eager Loading geladen?
[ ] Index vorhanden für alle WHERE-Bedingungen in Listenabfragen?
[ ] Kein SELECT * (unnötige Felder werden geladen)?
[ ] Pagination: Cursor-basiert bei > 10.000 erwarteten Einträgen?
## Datenmenge
[ ] Response-Größe für typische Listenabfragen gemessen?
[ ] Sparse Fieldsets implementiert für Endpoints mit vielen Feldern?
[ ] Eingebettete Ressourcen optional (via ?embed=) statt immer geladen?
[ ] Binärdaten (Bilder, PDFs) nicht als Base64 in JSON eingebettet?
## Caching
[ ] GET-Endpoints: Cache-Control-Header gesetzt?
[ ] ETag für selten ändernde Ressourcen implementiert?
[ ] Vary: Accept-Encoding gesetzt wenn Compression aktiv?
[ ] Redis-Cache für teure Aggregationen vorhanden?
## Netzwerk
[ ] HTTP Compression in Nginx konfiguriert (gzip/brotli)?
[ ] Großformatige Listen als Streaming-Endpoint oder mit Pagination?
[ ] HTTP/2 oder HTTP/3 am Load Balancer konfiguriert?
5. Dokumentations-Checkliste: OpenAPI-Vollständigkeit
Ein neuer Endpoint ohne vollständige OpenAPI-Dokumentation ist eine Schuld, die sofort anwächst: Frontend-Entwickler werden informell dokumentieren (Notion, Slack, Kommentare), die inoffizielle Dokumentation weicht vom Code ab und das Team verliert die Quelle der Wahrheit. Die Dokumentations-Checkliste im Review stellt sicher, dass kein Endpoint gemappt wird, bevor die OpenAPI-Spezifikation vollständig ist – inklusive aller Fehlerantworten, Beispiele und Security-Anforderungen.
Spectral als automatischer Linter prüft einen großen Teil der OpenAPI-Vollständigkeit maschinell: Pflichtfelder, Naming Conventions und Fehlerformat-Anforderungen. Was Spectral nicht prüft: semantische Korrektheit (stimmen die Beispiele mit der Implementierung überein?) und Vollständigkeit der Beschreibungen (sind die Felder menschlich verständlich dokumentiert?). Diese Aspekte bleiben im manuellen Review.
# OpenAPI Documentation Completeness Checklist
# Every new endpoint must have all these elements before merge
paths:
/products/{id}:
get:
# [ ] operationId: camelCase, unique, beschreibend
operationId: getProduct
# [ ] summary: kurz und präzise
summary: "Retrieve a single product by ID"
# [ ] description: Wann verwenden? Was ist zu beachten?
description: |
Returns the full product representation including pricing and stock.
Requires 'products:read' scope. Cached for 5 minutes.
# [ ] tags: mindestens ein Tag für Gruppierung
tags: [Products]
# [ ] parameters: alle Pfad-, Query- und Header-Parameter dokumentiert
parameters:
- name: id
in: path
required: true
description: "Numeric product ID (positive integer)"
schema:
type: integer
minimum: 1
example: 42
# [ ] responses: ALLE möglichen Statuscodes dokumentiert
responses:
"200":
description: "Product found"
content:
application/json:
schema:
$ref: "#/components/schemas/Product"
# [ ] Beispiel vorhanden
example:
id: 42
name: "Mironsoft T-Shirt"
price: 29.99
# [ ] 401 und 403 wenn gesichert
"401":
$ref: "#/components/responses/Unauthorized"
# [ ] 404 wenn Ressource nicht immer existiert
"404":
$ref: "#/components/responses/NotFound"
# [ ] security: deklariert (oder security: [] wenn öffentlich)
security:
- BearerAuth: ["products:read"]
6. Test-Checkliste: Abdeckung und Szenarien
API-Tests in Symfony-Projekten folgen typischerweise drei Schichten: Unit-Tests für einzelne Services und Validators, Integration-Tests für Controller (mit echtem HTTP-Stack), und Contract-Tests, die Responses gegen das OpenAPI-Schema validieren. Eine Test-Checkliste im Review prüft, ob alle drei Schichten abgedeckt sind und ob die relevanten Testszenarien vorhanden sind. Ein häufiger Befund im Review: nur der Happy Path ist getestet, alle Fehlerpfade (401, 403, 404, 422) fehlen.
Besonders wichtig: Autorisierungs-Tests, die prüfen, ob User A auf Ressourcen von User B zugreifen kann. Diese Tests werden häufig vergessen, weil sie erfordern, zwei verschiedene authentifizierte User im Test zu verwenden. Im Symfony-Kontext: zwei WebTestCase-Clients mit verschiedenen JWT-Tokens, ein Request auf eine Ressource des anderen Users, erwarteter Statuscode 403. Diese Tests fangen IDOR-Schwachstellen ab, bevor sie in die Produktion gelangen.
request('GET', '/api/v2/orders/1', [], [], [
'HTTP_AUTHORIZATION' => 'Bearer ' . $this->getTokenForUser('user_1'),
]);
self::assertResponseStatusCodeSame(200);
self::assertResponseHeaderSame('Content-Type', 'application/json');
}
// [ ] Auth test: no token -> 401
public function testUnauthenticatedRequestReturns401(): void
{
$client = static::createClient();
$client->request('GET', '/api/v2/orders/1');
self::assertResponseStatusCodeSame(401);
self::assertResponseHeaderSame('Content-Type', 'application/problem+json');
}
// [ ] Authorization test: other user's order -> 403 (IDOR prevention)
public function testOtherUserCannotReadOrder(): void
{
$client = static::createClient();
$client->request('GET', '/api/v2/orders/1', [], [], [
'HTTP_AUTHORIZATION' => 'Bearer ' . $this->getTokenForUser('user_2'),
]);
self::assertResponseStatusCodeSame(403);
}
// [ ] Not found: non-existent ID -> 404 with ProblemDetails
public function testNonExistentOrderReturns404(): void
{
$client = static::createClient();
$client->request('GET', '/api/v2/orders/99999', [], [], [
'HTTP_AUTHORIZATION' => 'Bearer ' . $this->getTokenForUser('user_1'),
]);
self::assertResponseStatusCodeSame(404);
$body = json_decode($client->getResponse()->getContent(), true);
self::assertArrayHasKey('type', $body);
self::assertArrayHasKey('status', $body);
self::assertSame(404, $body['status']);
}
// [ ] Validation: missing required fields -> 422 with violations
public function testCreateOrderWithMissingFieldsReturns422(): void
{
$client = static::createClient();
$client->request('POST', '/api/v2/orders', [], [], [
'HTTP_AUTHORIZATION' => 'Bearer ' . $this->getTokenForUser('user_1'),
'CONTENT_TYPE' => 'application/json',
], '{}');
self::assertResponseStatusCodeSame(422);
$body = json_decode($client->getResponse()->getContent(), true);
self::assertArrayHasKey('violations', $body);
}
// [ ] Rate limit: exceeded -> 429 with Retry-After
public function testRateLimitExceededReturns429(): void
{
// Implementation-dependent: test with in-memory limiter set to limit 1
$this->markTestIncomplete('Rate limit test requires test-specific limiter config');
}
private function getTokenForUser(string $userId): string
{
return $_ENV['TEST_TOKEN_' . strtoupper($userId)] ?? 'test-token';
}
}
7. Deployment-Checkliste: Backward Compatibility und Migration
Deployment-Checklisten für APIs adressieren Fragen, die im Code-Review oft übersehen werden, weil sie über den Code hinausgehen: Ist diese Änderung backward-kompatibel? Gibt es Datenbank-Migrationen, die atomar mit dem Code-Deploy sein müssen? Müssen Consumers benachrichtigt werden? Diese Fragen sind besonders relevant für Teams, die öffentliche oder semi-öffentliche APIs betreiben, wo Konsumenten nicht gleichzeitig mit dem Server-Code aktualisiert werden können.
Breaking Changes werden mit oasdiff automatisch erkannt, wenn das Tool in der CI-Pipeline läuft. Was es nicht erkennt: semantische Breaking Changes, bei denen die Signatur gleich bleibt, aber das Verhalten sich ändert. Ein Endpoint, der zuvor paginated Ergebnisse mit data-Key zurückgegeben hat und nun direkt ein Array zurückgibt, ist ein Breaking Change, der kein Schema-Diff auslöst. Diese semantischen Änderungen müssen im Review manuell erkannt und dokumentiert werden.
8. Review-Kategorien im Vergleich: Häufigkeit und Impact
Nicht alle Checklisten-Kategorien haben denselben Impact und dieselbe Häufigkeit in der Praxis. Die Erfahrung aus Symfony-API-Reviews zeigt, welche Kategorien am häufigsten Findings produzieren und welche den größten Impact auf die API-Qualität haben.
| Review-Kategorie | Häufigkeit von Findings | Impact bei Übersehen | Automatisierbar? |
|---|---|---|---|
| Security / Autorisierung | Mittel | Sehr hoch (Datenleck) | Teilweise (Static Analysis) |
| Fehlerformate | Sehr hoch | Mittel (Client-Komplexität) | Ja (Contract-Tests) |
| Design / Statuscodes | Hoch | Hoch (Breaking Change) | Teilweise (Spectral) |
| Performance / N+1 | Mittel | Hoch (Produktionsausfall) | Nein (manuell nötig) |
| Tests / Abdeckung | Hoch | Mittel (spätere Bugs) | Ja (Coverage-Reports) |
9. Zusammenfassung
Eine systematische API-Review-Checkliste für Symfony-Teams macht Reviews schneller, vollständiger und reproduzierbarer. Die sieben Kategorien – Design, Security, Fehlerbehandlung, Performance, Dokumentation, Tests und Deployment – decken alle relevanten Dimensionen ab. Jede Kategorie hat eine klare Prüfstruktur, die auch unerfahrene Reviewer durch die wichtigsten Prüfpunkte führt. Automatisierbare Prüfungen (Spectral, Contract-Tests, Coverage-Reports) werden in die CI-Pipeline integriert und müssen nicht mehr manuell im Review geprüft werden.
Der wichtigste Aspekt einer Checkliste ist die konsequente Anwendung. Eine Checkliste, die im Review übersprungen wird, wenn Zeitdruck entsteht, bringt keinen Mehrwert. Teams, die die Checkliste als festen Bestandteil des PR-Templates verankern (GitHub/GitLab PR-Template mit Checkbox-Liste), stellen sicher, dass jeder PR die Checkliste explizit adressiert hat – und der Reviewer nachvollziehen kann, welche Punkte der Autor selbst geprüft hat.
REST API Review-Checkliste — Das Wichtigste auf einen Blick
Automatisierbar
Spectral für OpenAPI-Linting. Contract-Tests für Schema-Konformität. oasdiff für Breaking Changes. Coverage-Reports für Testabdeckung. In CI integrieren.
Manuell prüfen
Authorization/Ownership (IDOR). Semantische Breaking Changes. N+1-Problem per Profiler. Informationsdichte in Fehlermeldungen.
Höchster Impact
Security (Autorisierungslücken). Design-Fehler (führen zu Breaking Changes). Performance-Probleme (Produktionsausfälle). Immer zuerst prüfen.
PR-Template
Checkliste als Checkbox-Liste im PR-Template verankern. Autor füllt aus, Reviewer validiert. Macht Reviews nachvollziehbar und strukturiert.