Aller au contenu principal
LaPolaris lance ses formations, profitez de -40% jusqu'au 30 juin ! En savoir plus

Construire une API REST propre avec Symfony 7 et API Platform : guide pas à pas

API Platform transforme Symfony en machine à produire des APIs REST en quelques lignes de configuration. Ce guide vous montre comment construire une API complète, sécurisée et bien structurée.

Guides & tutoriels ·
Adel LATIBI
· ·
Construire une API REST propre avec Symfony 7 et API Platform : guide pas à pas
Symfony est depuis longtemps la référence du développement PHP en entreprise. Avec API Platform, il est devenu l'un des outils les plus puissants pour construire des APIs REST modernes. La combinaison des deux permet de générer des endpoints complets, documentés et sécurisés avec un minimum de code.
Ce guide suppose que vous avez des bases en Symfony (routing, contrôleurs, Doctrine). On va construire une API de gestion d'articles de blog, de zéro à une API sécurisée par JWT.
Installation et configuration initiale
# Créer un projet Symfony
composer create-project symfony/skeleton mon-api
cd mon-api

# Installer API Platform et les dépendances essentielles
composer require api
composer require lexik/jwt-authentication-bundle
composer require symfony/orm-pack
composer require symfony/maker-bundle --dev
Configurer la base de données dans le fichier .env :
DATABASE_URL="mysql://user:password@127.0.0.1:3306/mon_api"
Créer une entité exposée via l'API
La magie d'API Platform : l'attribut #[ApiResource] sur une entité Doctrine génère automatiquement tous les endpoints CRUD.
<?php
// src/Entity/Article.php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Delete;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Serializer\Annotation\Groups;

#[ORM\Entity]
#[ApiResource(
    operations: [
        new GetCollection(),
        new Get(),
        new Post(security: "is_granted('ROLE_USER')"),
        new Put(security: "is_granted('ROLE_USER') and object.getAuthor() == user"),
        new Delete(security: "is_granted('ROLE_ADMIN')"),
    ],
    normalizationContext: ['groups' => ['article:read']],
    denormalizationContext: ['groups' => ['article:write']],
)]
class Article
{
    #[ORM\Id, ORM\GeneratedValue, ORM\Column]
    #[Groups(['article:read'])]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    #[Assert\NotBlank]
    #[Assert\Length(max: 255)]
    #[Groups(['article:read', 'article:write'])]
    private string $title;

    #[ORM\Column(type: 'text')]
    #[Assert\NotBlank]
    #[Groups(['article:read', 'article:write'])]
    private string $content;

    #[ORM\Column]
    #[Groups(['article:read'])]
    private \DateTimeImmutable $createdAt;

    #[ORM\ManyToOne]
    #[Groups(['article:read'])]
    private ?User $author = null;

    public function __construct()
    {
        $this->createdAt = new \DateTimeImmutable();
    }

    // Getters et setters...
}
Avec cette seule entité, API Platform génère :
  • GET /api/articles — liste paginée
  • GET /api/articles/{id} — un article
  • POST /api/articles — créer (authentifié)
  • PUT /api/articles/{id} — modifier (auteur seulement)
  • DELETE /api/articles/{id} — supprimer (admin seulement)
Configurer l'authentification JWT
Générer les clés SSL pour JWT :
php bin/console lexik:jwt:generate-keypair
Configuration dans config/packages/lexik_jwt_authentication.yaml :
lexik_jwt_authentication:
    secret_key: '%env(resolve:JWT_SECRET_KEY)%'
    public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
    pass_phrase: '%env(JWT_PASSPHRASE)%'
    token_ttl: 3600
Configuration du firewall dans config/packages/security.yaml :
security:
    firewalls:
        login:
            pattern: ^/api/login
            stateless: true
            json_login:
                check_path: /api/login_check
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure

        api:
            pattern: ^/api
            stateless: true
            jwt: ~

    access_control:
        - { path: ^/api/login, roles: PUBLIC_ACCESS }
        - { path: ^/api/docs, roles: PUBLIC_ACCESS }
        - { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
Ajouter des filtres de recherche
API Platform propose des filtres natifs très puissants :
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Metadata\ApiFilter;

#[ApiResource(...)]
#[ApiFilter(SearchFilter::class, properties: [
    'title' => 'partial',
    'author.name' => 'exact',
])]
#[ApiFilter(OrderFilter::class, properties: ['createdAt', 'title'])]
class Article { ... }
Automatiquement, vos endpoints acceptent maintenant :
  • GET /api/articles?title=symfony — recherche partielle dans le titre
  • GET /api/articles?order[createdAt]=desc — tri par date
  • GET /api/articles?page=2 — pagination (incluse par défaut)
Personnaliser la sérialisation
Les groupes de sérialisation (normalizationContext / denormalizationContext) permettent de contrôler précisément ce qui est exposé en lecture et en écriture :
// En lecture (GET), on expose l'auteur avec son nom
#[Groups(['article:read'])]
private ?User $author = null;

// En écriture (POST/PUT), l'auteur n'est pas dans le payload
// Il sera assigné automatiquement dans un State Processor
State Processors : la logique métier
Pour assigner automatiquement l'auteur lors de la création :
<?php
// src/State/ArticleProcessor.php

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use Symfony\Bundle\SecurityBundle\Security;

class ArticleProcessor implements ProcessorInterface
{
    public function __construct(
        private ProcessorInterface $persistProcessor,
        private Security $security,
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        if ($data instanceof Article && $operation instanceof Post) {
            $data->setAuthor($this->security->getUser());
        }

        return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
    }
}
La documentation automatique
L'un des atouts majeurs d'API Platform : la documentation Swagger UI est générée automatiquement à partir de vos entités et annotations. Accessible à /api/docs, elle permet de tester tous les endpoints directement depuis le navigateur, avec la liste des paramètres, les schémas de réponse et la possibilité de s'authentifier.
Bonnes pratiques en production
  • Désactivez les formats inutiles : par défaut API Platform supporte JSON-LD, Hydra, JSON, HAL. Si vous n'avez besoin que de JSON, désactivez le reste pour simplifier les réponses.
  • Versionnez votre API : préfixez vos routes avec /api/v1/ pour pouvoir faire évoluer l'API sans casser les clients existants.
  • Limitez les opérations exposées : listez explicitement les opérations autorisées dans #[ApiResource] plutôt que de tout exposer par défaut.
  • Testez vos endpoints : Symfony fournit un client HTTP de test excellent pour les tests fonctionnels d'API.
Conclusion
API Platform est l'un des frameworks les plus productifs qui existent pour construire des APIs REST. La combinaison avec Symfony donne accès à un écosystème mature, bien documenté, et utilisé en production dans des entreprises de toutes tailles.
La courbe d'apprentissage existe — notamment pour comprendre la sérialisation, les State Processors et la configuration du firewall. Mais une fois ces concepts assimilés, la vitesse de développement est remarquable.