Cours NestJS · Module 1: Bases de NestJS

L'architecture de NestJS - bâtir l'Empire

6 min de lecture
Dans cette leçon7

Un premier projet tient d'ordinaire dans un fichier. Un deuxième en compte cinq. Au vingtième, plus personne ne sait où se trouve le calcul de la solde - dans un contrôleur, dans un utilitaire quelconque, ou peut-être à trois endroits à la fois, chacun légèrement différent.

Rome n'a pas grandi en ajoutant des hommes à une seule cohorte. Elle a grandi parce que chacun avait un rôle et que tous savaient lequel : le messager porte les ordres, le quartier-maître compte les réserves, le scribe tient le registre. NestJS impose la même répartition - et c'est cette structure imposée qui permet de retrouver le vingtième fichier.

Ce sur quoi tout repose

Le fondement de l'architecture de NestJS, ce sont les modules, les contrôleurs et les providers reliés par l'injection de dépendances (Dependency Injection). NestJS a emprunté cette organisation à Angular - la documentation du framework dit clairement que son architecture est fortement inspirée d'Angular.

Tu as peut-être entendu parler du motif Model-View-Controller (MVC). NestJS sait fonctionner ainsi quand le serveur génère des pages HTML à partir de templates, mais c'est une technique optionnelle, pas le fondement. Un backend classique n'a pas du tout de couche View - la réponse est du JSON, non une page HTML.

À l'intérieur de NestJS, tu rencontreras aussi d'autres motifs connus, par exemple chaque provider est par défaut un singleton - NestJS en crée une seule instance pour toute l'application. Mais c'est un détail : la forme de toute l'application, ce sont les modules et l'injection de dépendances qui la donnent.

Trois rôles

Toute application NestJS se compose de trois éléments récurrents. Il vaut la peine de les retenir par la question à laquelle chacun répond :

  • Le contrôleur - qui reçoit la requête ? Il traite les requêtes HTTP et renvoie les réponses. Il ne stocke pas de données, ne s'occupe pas de l'apparence, ne compile rien.
  • Le service - comment le faire ? C'est là que vit la logique.
  • Le module - qu'est-ce qui va ensemble ? Il regroupe les deux et les déclare à NestJS.

Le service - là où vit la logique

Commençons par le milieu, car c'est le service qui porte la vraie valeur :

1@Injectable()
2export class LegionService {
3  private legions = [
4    { id: 1, name: 'Legio X Equestris' },
5    { id: 2, name: 'Legio III Gallica' },
6  ];
7
8  findAll() {
9    return this.legions;
10  }
11}

L'ordre de déclaration est fixe : @Injectable(), puis export class, ensuite le nom de la classe, et enfin le corps avec ses méthodes.

Et voici la réponse à la question qui, en pratique, décide de la qualité de tout un projet : la logique métier appartient aux services - ce que NestJS appelle les providers. Pas aux contrôleurs, pas aux middlewares, pas aux fichiers de configuration.

La raison est simple. Une logique placée dans un contrôleur est liée à HTTP - tu ne pourras pas l'appeler depuis une tâche par lots ni depuis un test unitaire sans simuler une requête. Dans un middleware, c'est pire encore, car le middleware travaille sur la requête brute, avant qu'on sache où elle va. Une logique dans un service est une méthode ordinaire d'une classe ordinaire - tu l'appelles d'où tu veux.

Le contrôleur - qui reçoit la requête

Le contrôleur prend le service par son constructeur et lui délègue le travail :

1@Controller('legions')
2export class LegionController {
3  constructor(private readonly legionService: LegionService) {}
4
5  @Get()
6  findAll() {
7    return this.legionService.findAll();
8  }
9}

Note les proportions. La méthode du contrôleur tient en une ligne - et c'est ainsi qu'il faut faire. Tout son rôle est de recevoir la requête et de désigner l'exécutant.

Le contrôleur ne crée pas le service par new. Il l'inscrit dans le constructeur et NestJS le lui remet tout prêt - c'est l'injection de dépendances, un mécanisme que tu découvriras en détail dans les leçons suivantes. Pour l'instant, il suffit d'en voir l'effet : le contrôleur dit ce dont il a besoin et ne se soucie pas d'où le prendre.

Le module - ce qui va ensemble

Le troisième élément relie les deux précédents :

1@Module({
2  controllers: [LegionController],
3  providers: [LegionService],
4})
5export class LegionModule {}

Sans cette déclaration, NestJS ignore que ces classes existent - des fichiers ne signifient rien par eux-mêmes.

Remarque qu'un module montre toute la province d'un coup : qui reçoit les requêtes et qui accomplit le travail. En ouvrant un projet inconnu, on lit les modules en premier, car ce sont eux qui donnent la carte.

La route d'une requête

Assemblons le tout en un seul parcours. Une requête GET /legions voyage ainsi : elle atteint le contrôleur LegionController, qui appelle une méthode du service LegionService, le service renvoie les données, le contrôleur les restitue en réponse. Le module ne participe pas au vol - il a agi plus tôt, au démarrage, quand NestJS a bâti à partir de lui sa carte des dépendances.

Ce schéma se répète dans toute l'application, quelle que soit sa taille. La vingtième ressource ressemble exactement à la première - et c'est pourquoi tu sais où chercher le calcul de la solde : dans un service, toujours dans un service.

Récapitulatif

L'Empire a son administration et chacun connaît son rôle :

  • le fondement de l'architecture de NestJS, ce sont les modules, les contrôleurs et les providers reliés par l'injection de dépendances - une organisation empruntée à Angular,
  • MVC est une technique optionnelle pour générer des pages à partir de templates ; un backend qui renvoie du JSON n'a pas de couche View,
  • un contrôleur traite les requêtes HTTP et renvoie les réponses - il ne stocke pas de données et ne s'occupe pas de l'apparence,
  • la logique métier appartient aux services (providers) - pas aux contrôleurs, aux middlewares ni aux fichiers de configuration,
  • la raison : une logique dans un service est une méthode ordinaire, tu peux donc l'appeler hors HTTP,
  • déclaration d'un service dans l'ordre : @Injectable(), export class, le nom, le corps avec les méthodes,
  • un contrôleur reçoit le service par son constructeur, il ne le crée pas par new,
  • une méthode de contrôleur tient d'ordinaire en une ligne - recevoir et déléguer,
  • un module déclare les contrôleurs et les providers ; sans lui, NestJS ne les voit pas,
  • la route d'une requête : contrôleur → service → données → réponse ; le module a agi plus tôt, au démarrage.

Dans la prochaine leçon, nous examinerons les modules de plus près - tu verras comment ils divisent une application en provinces et ce qui décide qu'une province en voie une autre. En attendant, retiens ceci : le contrôleur reçoit, le service exécute, le module enregistre - et le même agencement se répète dans chaque recoin de l'application.

Code de cette leçon : src/imperium.module.ts
1// Module Imperium - organisation des légions et des impôts
2import { Module } from '@nestjs/common';
3import { LegionController } from './legion.controller';
4import { TributumController } from './tributum.controller';
5import { ProvinciaController } from './provincia.controller';
6import { LegionService } from './legion.service';
7import { TributumService } from './tributum.service';
8import { ProvinciaService } from './provincia.service';
9import { AquaeductService } from './aquaeduct.service';
10
11console.log("Chargement de l'architecture Imperium Romanum...");
12
13// ===========================================
14// 1. Module de légion
15// ===========================================
16@Module({
17  controllers: [LegionController],
18  providers: [LegionService],
19  exports: [LegionService],
20})
21export class LegionModule {
22  constructor() {
23    console.log("Module de légion initialisé - Prêt à commander les armées !");
24  }
25}
26
27// ===========================================
28// 2. Module d'impôts - Module Tributum
29// ===========================================
30@Module({
31  imports: [LegionModule],
32  controllers: [TributumController],
33  providers: [TributumService],
34  exports: [TributumService],
35})
36export class TributumModule {
37  constructor() {
38    console.log("Module Tributum initialisé - Prêt à collecter les impôts !");
39  }
40}
41
42// ===========================================
43// 3. Module de province - Module Provincia
44// ===========================================
45@Module({
46  imports: [LegionModule, TributumModule],
47  controllers: [ProvinciaController],
48  providers: [ProvinciaService, AquaeductService],
49  exports: [ProvinciaService, AquaeductService],
50})
51export class ProvinciaModule {
52  constructor() {
53    console.log("Module Provincia initialisé - Toutes les régions sont prêtes !");
54  }
55}
56
57// ===========================================
58// 4. Module principal de l'application - App Module
59// ===========================================
60@Module({
61  imports: [
62    LegionModule,
63    TributumModule,
64    ProvinciaModule,
65  ],
66  controllers: [],
67  providers: [
68    {
69      provide: 'IMPERIUM_CONFIG',
70      useValue: {
71        imperiumName: 'Roma Aeterna NestJS',
72        caesar: 'Augustus.js',
73        version: '1.0.0',
74        port: 3000,
75      }
76    }
77  ],
78})
79export class ImperiumAppModule {
80  constructor() {
81    console.log("Module App Imperium chargé avec succès !");
82    console.log("Tous les systèmes sont opérationnels - Prêt pour la conquête !");
83  }
84}
85
86// ===========================================
87// 5. Démonstration de l'injection de dépendances
88// ===========================================
89export class DependencyInjectionDemo {
90  static demonstrateModuleArchitecture() {
91    console.log("\n=== ARCHITECTURE DES MODULES ===");
92    console.log("LegionModule : Gère les légions");
93    console.log("   ├── LegionController (endpoints REST)");
94    console.log("   ├── LegionService (logique métier)");
95    console.log("   └── exports: [LegionService]");
96
97    console.log("\nTributumModule : Gère les impôts");
98    console.log("   ├── imports: [LegionModule]");
99    console.log("   ├── TributumController (endpoints REST)");
100    console.log("   ├── TributumService (logique métier)");
101    console.log("   └── exports: [TributumService]");
102
103    console.log("\nProvinciaModule : Gère les provinces");
104    console.log("   ├── imports: [LegionModule, TributumModule]");
105    console.log("   ├── ProvinciaController (endpoints REST)");
106    console.log("   ├── ProvinciaService (logique métier)");
107    console.log("   ├── AquaeductService (logique d'infrastructure)");
108    console.log("   └── exports: [ProvinciaService, AquaeductService]");
109
110    console.log("\nImperiumAppModule : Module principal");
111    console.log("   ├── imports: [LegionModule, TributumModule, ProvinciaModule]");
112    console.log("   ├── providers: [IMPERIUM_CONFIG]");
113    console.log("   └── Configurations globales");
114  }
115
116  static demonstrateDependencyFlow() {
117    console.log("\n=== FLUX DES DÉPENDANCES ===");
118    console.log("1. Requête → ProvinciaController");
119    console.log("2. ProvinciaController → ProvinciaService (DI)");
120    console.log("3. ProvinciaService → LegionService (DI via import)");
121    console.log("4. ProvinciaService → TributumService (DI via import)");
122    console.log("5. ProvinciaService → AquaeductService (DI)");
123    console.log("6. Réponse ← ProvinciaController");
124
125    console.log("\nAvantages de l'injection de dépendances :");
126    console.log("   Couplage faible");
127    console.log("   Tests faciles");
128    console.log("   Réutilisabilité du code");
129    console.log("   Maintenabilité");
130  }
131}
132
133DependencyInjectionDemo.demonstrateModuleArchitecture();
134DependencyInjectionDemo.demonstrateDependencyFlow();
135
136export { LegionModule, TributumModule, ProvinciaModule };

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. De quels éléments se compose l'architecture d'une application NestJS ?

  2. 2. Quel est le rôle principal du contrôleur dans l'architecture de NestJS ?

  3. 3. Où la logique métier doit-elle se trouver selon l'architecture de NestJS ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Écris un service LegionService avec le décorateur @Injectable() et une méthode findAll() qui renvoie un tableau de légionnaires (au moins un), par exemple [{ id: 1, name: 'Marcus Aurelius' }].

  • Tri vertical

    Remets dans le bon ordre les éléments de la déclaration d'un service NestJS :

  • Éditeur de code

    Écris un contrôleur LegionController avec le décorateur @Controller('legions') qui reçoit LegionService via le constructeur et qui, dans une méthode findAll() avec le décorateur @Get(), renvoie le résultat de this.legionService.findAll().