APIs sind das Rückgrat moderner Webanwendungen. Ein durchdachtes API-Design erleichtert die Zusammenarbeit zwischen Frontend und Backend und macht die Schnittstelle langfristig wartbar.
Konsistente URL-Struktur
RESTful URLs sollten Ressourcen beschreiben, nicht Aktionen:
GET /api/v1/products # Liste aller Produkte
GET /api/v1/products/42 # Einzelnes Produkt
POST /api/v1/products # Neues Produkt erstellen
PUT /api/v1/products/42 # Produkt aktualisieren
DELETE /api/v1/products/42 # Produkt löschen
In Symfony nutze ich dafür Attribute-basiertes Routing:
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/api/v1/products')]
class ProductApiController extends AbstractController
{
#[Route('', methods: ['GET'])]
public function list(): JsonResponse
{
// Pagination, Filter, Sorting
return $this->json([
'data' => $products,
'meta' => [
'total' => $total,
'page' => $page,
'per_page' => $perPage,
],
]);
}
#[Route('/{id}', methods: ['GET'])]
public function show(int $id): JsonResponse
{
// Single resource
return $this->json(['data' => $product]);
}
}
Einheitliche Fehlerbehandlung
Ein konsistentes Fehlerformat ist entscheidend. Ich verwende einen zentralen Exception-Listener:
<?php
declare(strict_types=1);
namespace App\EventListener;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class ApiExceptionListener
{
public function onKernelException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
$statusCode = $exception instanceof HttpExceptionInterface
? $exception->getStatusCode()
: 500;
$response = new JsonResponse([
'error' => [
'code' => $statusCode,
'message' => $exception->getMessage(),
],
], $statusCode);
$event->setResponse($response);
}
}
Validierung und DTOs
Eingehende Daten sollten immer über DTOs validiert werden:
<?php
declare(strict_types=1);
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
class CreateProductRequest
{
#[Assert\NotBlank]
#[Assert\Length(min: 3, max: 255)]
public string $name;
#[Assert\NotBlank]
#[Assert\Positive]
public float $price;
#[Assert\NotBlank]
public string $description;
}
Versionierung
API-Versionierung über die URL (/api/v1/) ist einfach und explizit. Für größere Projekte kann auch Header-basierte Versionierung sinnvoll sein. Wichtig ist, dass eine einmal veröffentlichte API-Version stabil bleibt und Breaking Changes nur in neuen Versionen eingeführt werden.
Ein gut designtes API spart langfristig viel Entwicklungszeit – sowohl beim eigenen Team als auch bei externen Konsumenten der Schnittstelle.
Kommentare
Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.