Building RESTful APIs with Symfony

Symfony is a powerful PHP framework that excels at building robust, scalable APIs. Let me share some best practices I’ve learned while building production APIs.

Why Symfony for APIs?

Symfony offers several advantages for API development:

  • Mature ecosystem: Well-tested components
  • Flexibility: Use only what you need
  • Performance: Optimized for production workloads
  • Developer experience: Excellent debugging tools

Project Setup

Starting a new Symfony API project is straightforward:

composer create-project symfony/skeleton my-api
cd my-api
composer require api

This installs the minimal dependencies needed for an API, including:

  • API Platform (optional but powerful)
  • Serializer component
  • Validator component

Creating Your First Endpoint

Here’s a simple controller for a REST API:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

class ProductController extends AbstractController
{
    #[Route('/api/products', name: 'product_list', methods: ['GET'])]
    public function list(): JsonResponse
    {
        $products = [
            ['id' => 1, 'name' => 'Product 1', 'price' => 29.99],
            ['id' => 2, 'name' => 'Product 2', 'price' => 39.99],
        ];

        return $this->json($products);
    }
}

Best Practices

1. Use DTOs (Data Transfer Objects)

Decouple your API responses from your entities:

class ProductDTO
{
    public function __construct(
        public readonly int $id,
        public readonly string $name,
        public readonly float $price
    ) {}
}

2. Validation

Always validate input data:

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;
}

3. Error Handling

Implement consistent error responses:

class ApiExceptionSubscriber implements EventSubscriberInterface
{
    public function onKernelException(ExceptionEvent $event): void
    {
        $exception = $event->getThrowable();
        
        $response = new JsonResponse([
            'error' => $exception->getMessage(),
            'code' => $exception->getCode(),
        ], 400);

        $event->setResponse($response);
    }
}

Testing

Symfony’s testing tools make API testing easy:

public function testProductList(): void
{
    $client = static::createClient();
    $client->request('GET', '/api/products');

    $this->assertResponseIsSuccessful();
    $this->assertResponseHeaderSame('content-type', 'application/json');
}

Next Steps

In future posts, I’ll cover:

  • Authentication with JWT
  • Rate limiting strategies
  • API versioning
  • Performance optimization with Doctrine

Happy coding!