Réduire le boilerplate backend avec des services génériques
Notes depuis une expérimentation autour de comportements CRUD réutilisables avec NestJS et Fastify.
- nestjs
- fastify
- backend
Les projets backend commencent souvent avec une répétition saine. Quelques controllers, quelques services, quelques DTOs, et tout reste explicite. Puis le projet grandit, et la même forme CRUD revient encore et encore.
Le motif qui se répète
Beaucoup de ressources ont besoin du même comportement de base :
- créer un enregistrement
- lire un ou plusieurs enregistrements
- mettre à jour un enregistrement
- supprimer ou archiver un enregistrement
- valider les entrées
- filtrer et paginer les résultats
- retourner une réponse prévisible
Au début, répéter ce code est acceptable. Cela garde le système évident. Mais après assez de modules, la répétition n’ajoute plus de clarté. Elle devient du poids de maintenance.
C’est le problème que je voulais explorer avec une approche de web service générique.
L’expérience
L’idée était d’utiliser NestJS, Fastify, TypeScript, la réflexion et des métadonnées pour générer des comportements CRUD réutilisables, tout en gardant de l’espace pour les règles métier.
Le but n’était pas de créer un backend magique. La magie coûte souvent cher plus tard. Le but était de retirer les parties ennuyeuses qui sont faciles à définir et difficiles à justifier quand on les réécrit encore.
Une couche générique peut gérer la forme partagée :
- structure de routes
- méthodes de service de base
- flux de validation commun
- formatage des réponses
- filtres simples
Mais elle ne doit pas avaler la logique produit.
| Peut être générique | Doit rester explicite |
|---|---|
| Pagination | Règles de prix |
| Filtres simples | Workflow de paiement |
| Format des réponses | Décisions de permission |
| Méthodes CRUD communes | Validation spécifique au domaine |
type GenericResourceConfig = {
model: string;
searchableFields: string[];
allowedFilters: string[];
beforeCreate?: "domain hook";
};
Où l’abstraction doit s’arrêter
La question de design la plus importante n’est pas “est-ce que cela peut être générique ?” mais “est-ce que cela doit rester explicite ?”
Le CRUD peut souvent être partagé. Une règle de prix, un workflow de paiement, une étape d’onboarding ou une décision de permission devrait généralement rester visible dans le code applicatif. Ce n’est pas du boilerplate. C’est le produit.
Cette frontière compte parce qu’une mauvaise abstraction cache les décisions. Une bonne abstraction retire le bruit autour des décisions.
Ce que je garderais
Si je continue cette expérimentation, je garderais la couche générique petite et prévisible. Elle devrait être facile à inspecter, facile à surcharger et facile à retirer d’un module qui a besoin d’un comportement spécifique.
La version utile de cette idée n’est pas un framework dans le framework. C’est un outil qui aide le backend à rester explicite là où ça compte, et plus rapide là où ça ne compte pas.