Développement API REST – Au-delà du CRUD
REST est partout – mais la plupart des APIs REST ne sont que des tables de base de données exposées en HTTP. GET, POST, PUT, DELETE mappés sur du CRUD, avec toute la logique métier qui fuit dans les contrôleurs ou, pire, qui est laissée au frontend. Après des années de conception d’APIs web en PHP/Symfony et TypeScript/Node.js, je conçois des APIs qui expriment l’intention métier, appliquent les règles du domaine et guident le client vers les actions disponibles – pas juste servir de la donnée.
REST est orienté CRUD. Votre domaine ne l’est pas.
Le vocabulaire REST standard – GET, POST, PUT,
DELETE – se mappe naturellement sur créer/lire/modifier/supprimer.
Ça fonctionne pour des ressources simples. Mais les opérations métier réelles
rentrent rarement dans « mettre à jour un enregistrement ».
Prenons un système de gestion de commandes. Le client ne « met à jour »
pas une commande – il la confirme, l’annule,
demande un remboursement, assigne un transporteur. Chacune de
ces actions est un use case distinct avec ses propres préconditions, règles
métier et effets de bord. Tout cacher derrière un seul PUT /orders/{id},
c’est perdre de l’information – et déléguer la décision au client.
PATCH comme déclencheur de use case
Une approche que j’utilise pour concilier les conventions REST avec la
clean architecture : utiliser PATCH pour déclencher
des use cases spécifiques. Au lieu d’un « update » générique,
chaque requête PATCH cible une opération nommée :
Chaque route est mappée sur un command handler dédié dans la couche applicative. Le contrôleur est un simple adaptateur : il désérialise la requête, construit la commande, la dispatche. Pas de logique métier dans le contrôleur. Pas d’ambiguïté sur l’intention de l’appelant.
Est-ce du REST « pur » ? Les puristes peuvent débattre. Mais REST
n’a jamais été une question de pureté – c’est une question de
sémantique HTTP pour rendre l’intention explicite. Un PATCH
qui modifie une partie de l’état d’une ressource via une opération métier
bien définie est plus expressif qu’un PUT qui remplace la représentation
entière en espérant que le serveur comprendra ce qui a changé.
CQRS – séparer commandes et requêtes au niveau de l’API
L’approche décrite plus haut mène naturellement au CQRS (Command Query Responsibility Segregation). Quand vos routes d’API distinguent explicitement « ce qui lit » de « ce qui change l’état », l’architecture se clarifie à chaque couche.
Queries – des modèles de lecture optimisés
Les endpoints GET retournent des projections optimisées pour
la lecture, pas des dumps d’entités bruts. Le côté query
peut utiliser des vues dénormalisées, des index Elasticsearch ou des DTOs
dédiés – ce qui sert le mieux le consommateur. Plus de surprises de
lazy-loading Doctrine. Plus de requêtes N+1. Le modèle de lecture est
conçu pour l’écran, pas pour l’ORM.
Commands – intention explicite, effets de bord explicites
Les endpoints POST et PATCH dispatchent des commandes
vers des handlers dédiés. Chaque handler valide les préconditions, applique les
règles métier et peut émettre des événements de domaine
(commande confirmée, transporteur assigné, facture générée).
Le handler est là où le domaine vit – pas le contrôleur, pas l’ORM.
Dans un contexte Symfony, le composant Messenger fournit un command bus naturel. Les commandes sont de simples value objects, les handlers sont des services. Le dispatcher ne se soucie pas de savoir si la commande vient d’une requête HTTP, d’une commande CLI ou d’un message asynchrone – ce qui est exactement le but de la clean architecture.
HATEOAS – l’API dit au client ce qu’il peut faire
L’un des principes les plus sous-utilisés en conception d’API REST est HATEOAS (Hypermedia As The Engine Of Application State). L’idée est simple : la réponse de l’API inclut des liens qui décrivent les actions disponibles en fonction de l’état actuel de la ressource.
Une fois la commande confirmée, le lien confirm disparaît et de nouveaux
liens apparaissent (assign-carrier, request-refund). Le
frontend n’a pas besoin de connaître les règles métier
qui déterminent les transitions valides – l’API le lui dit.
Pourquoi c’est important pour les équipes front
Sans HATEOAS, le frontend duplique la logique métier : « si le statut est pending et le paiement confirmé et l’utilisateur a le rôle manager, alors afficher le bouton confirmer ». Cette logique est fragile, dispersée dans les composants et dérive des règles backend avec le temps.
Avec les liens HATEOAS, la logique front devient : « si le lien existe, afficher le bouton ». La complexité reste là où elle doit être – dans la couche domaine du backend. Le frontend devient une couche de rendu légère qui suit les instructions de l’API.
C’est particulièrement précieux dans les applications B2B complexes où les règles métier évoluent fréquemment. Un changement de règle côté backend ajuste automatiquement ce que le frontend peut afficher – sans redéployer le front, sans coordonner les releases.
Principes de conception d’API que j’applique
Contract-first
Définir le contrat d’API (OpenAPI / JSON Schema) avant d’écrire du code. Le contrat est le langage partagé entre équipes back et front. Il alimente la génération de code, la documentation et les tests automatisés.
Codes HTTP avec du sens
201 Created pour la création. 409 Conflict pour les
violations de règles métier. 422 Unprocessable Entity pour les
erreurs de validation. Tout n’est pas un 200 ou un 500.
Stratégie de versioning
Versioning par URL (/v1/) pour les APIs publiques, par header pour les APIs
internes. Politiques de dépréciation avec headers sunset et
guides de migration – pas de breaking changes silencieux.
Pagination, filtrage, tri
Pagination par curseur pour les gros volumes. Conventions de filtrage cohérentes. Pas de paramètres surprise – chaque filtre documenté dans la spec OpenAPI.
Erreurs comme citoyens de première classe
Payloads d’erreur structurés avec codes machine-readable, messages humains et détails de validation par champ. Le client peut toujours traiter l’erreur programmatiquement, pas juste afficher un message générique.
Sécurité by design
Authentification JWT, flux OAuth2, rate limiting, validation des entrées à la frontière. OWASP API Security Top 10 comme base – pas comme réflexion après coup.
Un mot sur API Platform
API Platform est un outil puissant pour générer des APIs REST et GraphQL à partir d’entités Symfony avec un minimum de code. Pour des APIs CRUD classiques, il délivre vite : pagination, filtres, documentation OpenAPI – tout inclus.
Mais quand votre API doit exprimer des opérations métier au-delà du CRUD – quand vous avez besoin de CQRS, de transitions d’état custom, de workflows pilotés par HATEOAS – les abstractions d’API Platform (State Providers, Processors) commencent à contraindre plus qu’accélérer. L’outil fonctionne au mieux quand le domaine est CRUD-shaped. Quand il ne l’est pas, on passe plus de temps à contourner le framework qu’à coder le domaine.
J’ai utilisé API Platform sur plusieurs projets (notamment chez IAD, où je détaille l’expérience). Mon avis : à utiliser quand le fit CRUD est réel. Pour des APIs orientées use cases avec une logique métier riche, des contrôleurs Symfony standard avec un command bus offrent plus de contrôle avec moins de friction.
Stack technique pour le développement d’API
- PHP/Symfony : contrôleurs Symfony, Messenger (command bus), Serializer, Validator, Security, API Platform (avec DTOs pour s’intégrer en clean architecture)
- TypeScript/Node.js : NestJS, Apollo GraphQL, Express
- Documentation : OpenAPI 3.x, JSON Schema, Swagger UI
- Tests : tests fonctionnels PHPUnit, Behat, Jest, Postman/Newman
- Bases de données : PostgreSQL, MongoDB, Elasticsearch, DynamoDB
- Authentification : JWT, OAuth2, clés API
- Asynchrone : Symfony Messenger, RabbitMQ, AWS SQS, événements de domaine