APIs are the backbone of modern web applications. A well-thought-out API design facilitates collaboration between frontend and backend and keeps the interface maintainable in the long run.
Consistent URL Structure
RESTful URLs should describe resources, not actions:
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, I use attribute-based routing for this:
<?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]);
}
}
Unified Error Handling
A consistent error format is crucial. I use a central 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);
}
}
Validation and DTOs
Incoming data should always be validated through DTOs:
<?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;
}
Versioning
API versioning via the URL (/api/v1/) is simple and explicit. For larger projects, header-based versioning can also be appropriate. The important thing is that a published API version remains stable and breaking changes are only introduced in new versions.
A well-designed API saves significant development time in the long run -- both for your own team and for external consumers of the interface.
Kommentare
Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.