10 min de lecture

Versionner une API REST avec Symfony : URL, en-têtes et déploiement

Comparaison praticable URI /v1 vs négociation par en-têtes, impacts cache et clients, extraits routes et contrôleurs Symfony.

  • API REST
  • Symfony
  • HTTP
Schéma : clients et versions d’API SPA Mobile Partenaire API /api/v1 … v2 Compatibilité & déploiement

Version dans le chemin (URI)

Exemple : /api/v1/invoices, /api/v2/invoices. Avantages : lisible dans les logs, facile à routager dans Symfony (prefix / attributs de route), compatible avec des règles de cache CDN très explicites. Inconvénient : les URL « vivent » longtemps — il faut une politique de décommission claire.

# config/routes.yaml (extrait illustratif)
api_v1:
    resource: ../src/Controller/Api/V1/
    type: attribute
    prefix: /api/v1

api_v2: resource: ../src/Controller/Api/V2/ type: attribute prefix: /api/v2

Version par en-tête ou media type

Exemple : Accept: application/vnd.example.v2+json. Utile quand vous voulez une URL stable et que vos clients savent configurer des en-têtes (peu fréquent côté navigateur pur, plus courant serveur-à-serveur). Symfony peut discriminer via Request::getPreferredFormat() ou des RequestMatcher — mais la documentation OpenAPI doit être irréprochable sinon les intégrations dérivent.

Ce que les proxies et caches voient

Si une ressource change de forme sans changer d’URL ni d’en-tête de variance, vous risquez des réponses mises en cache incohérentes. Avec URI versionnée, la clé de cache est naturellement séparée. Avec Accept, configurez Vary: Accept et vérifiez le comportement de votre CDN.

Contrôleur : garder le métier partagé

Anti-pattern fréquent : dupliquer 80 % de la logique entre V1 et V2. Mieux : services applicatifs stables, adapters minces par version pour la forme de la réponse :

// Pseudo-code : même service, représentation différente.
final class InvoiceController {
    public function __construct(private InvoiceRepository $repo) {}
#[Route('/invoices/{id}', methods: ['GET'])]
public function showV1(string $id, InvoiceV1Normalizer $n): JsonResponse {
    return new JsonResponse($n->normalize($this->repo->get(Uuid::fromString($id))));
}

}

FAQ

Faut-il supporter V1 indéfiniment ?

Non : fixez une date ou une release de fin, communiquez-la dans la doc et renvoyez des en-têtes Deprecation / Sunset lorsque pertinent pour les clients API.

GraphQL remplace-t-il le versionnement REST ?

Ce sont des compromis différents : GraphQL déplace la complexité (schéma, N+1, auth par champ). Ce n’est pas un raccourci magique pour éviter le contrat et la compatibilité.