cd ../log

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.

Diagramme montrant le comportement CRUD générique séparé de la logique produit explicite
L'abstraction utile retire la structure répétée sans cacher les décisions produit.

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ériqueDoit rester explicite
PaginationRègles de prix
Filtres simplesWorkflow de paiement
Format des réponsesDécisions de permission
Méthodes CRUD communesValidation 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.