À propos de ce cours
Objectifs de la leçon
- Connaître le rôle des quatre propriétés de @Module.
- Partager un service entre deux modules (exports + imports).
- Comprendre l'encapsulation des providers.
- Lire et corriger l'erreur « Nest can't resolve dependencies ».
Les quatre propriétés de @Module
| Propriété | Contient | Signification |
|---|---|---|
controllers | des classes @Controller | les contrôleurs créés par ce module ; leurs routes sont enregistrées |
providers | des classes @Injectable (ou des providers personnalisés, chapitre 07) | les services créés par ce module, injectables à l'intérieur du module |
exports | une partie des providers (ou des modules importés) | ce que le module rend disponible aux modules qui l'importent |
imports | d'autres modules | les modules dont on veut utiliser les providers exportés |
Retenez la règle d'or : un provider est privé à son module. Pour l'utiliser ailleurs, il faut à la fois qu'il soit exporté par son module et que ce module soit importé là où on l'utilise.
StagiairesModule importe GroupesModule : il voit GroupesService (exporté) mais pas CodesService (privé).
Un exemple complet : groupes et stagiaires
Un stagiaire appartient à un groupe. La route GET /api/stagiaires/:id doit renvoyer le stagiaire avec le détail de son groupe, fourni par GroupesService. Générez la structure :
nest g mo groupes
nest g co groupes --no-spec
nest g s groupes --no-spec
nest g s groupes/codes --flat --no-spec
nest g mo stagiaires
nest g co stagiaires --no-spec
nest g s stagiaires --no-spec
Le module des groupes: un service interne (CodesService), un service exporté (GroupesService) et un contrôleur.
📄 src/groupes/codes.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class CodesService { // service interne : NON exporté
normaliser(code: string): string {
return code.trim().toUpperCase();
}
}
📄 src/groupes/groupes.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { CodesService } from './codes.service';
export interface Groupe {
code: string;
filiere: string;
}
@Injectable()
export class GroupesService {
private groupes: Groupe[] = [
{ code: 'DEV101', filiere: 'Développement digital' },
{ code: 'DEV102', filiere: 'Développement digital' },
];
constructor(private readonly codes: CodesService) {}
findAll(): Groupe[] {
return this.groupes;
}
findOne(code: string): Groupe {
const g = this.groupes.find((x) => x.code === this.codes.normaliser(code));
if (!g) throw new NotFoundException(`Groupe ${code} introuvable`);
return g;
}
}
📄 src/groupes/groupes.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { GroupesService } from './groupes.service';
@Controller('groupes')
export class GroupesController {
constructor(private readonly groupesService: GroupesService) {}
@Get()
findAll() {
return this.groupesService.findAll();
}
@Get(':code')
findOne(@Param('code') code: string) {
return this.groupesService.findOne(code);
}
}
📄 src/groupes/groupes.module.ts
import { Module } from '@nestjs/common';
import { CodesService } from './codes.service';
import { GroupesController } from './groupes.controller';
import { GroupesService } from './groupes.service';
@Module({
controllers: [GroupesController],
providers: [GroupesService, CodesService],
exports: [GroupesService], // seul GroupesService est visible à l'extérieur
})
export class GroupesModule {}
Le module des stagiaires importe GroupesModule pour pouvoir injecter GroupesService:
📄 src/stagiaires/stagiaires.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { GroupesService } from '../groupes/groupes.service';
@Injectable()
export class StagiairesService {
private stagiaires = [
{ id: 1, nom: 'Ben Ali', groupe: 'DEV101' },
{ id: 2, nom: 'Mansouri', groupe: 'DEV102' },
];
constructor(private readonly groupesService: GroupesService) {} // vient de GroupesModule
findOne(id: number) {
const s = this.stagiaires.find((x) => x.id === id);
if (!s) throw new NotFoundException(`Stagiaire ${id} introuvable`);
return { ...s, groupe: this.groupesService.findOne(s.groupe) }; // groupe détaillé
}
}
📄 src/stagiaires/stagiaires.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { StagiairesService } from './stagiaires.service';
@Controller('stagiaires')
export class StagiairesController {
constructor(private readonly stagiairesService: StagiairesService) {}
@Get(':id')
findOne(@Param('id') id: string) {
return this.stagiairesService.findOne(Number(id));
}
}
📄 src/stagiaires/stagiaires.module.ts
import { Module } from '@nestjs/common';
import { GroupesModule } from '../groupes/groupes.module';
import { StagiairesController } from './stagiaires.controller';
import { StagiairesService } from './stagiaires.service';
@Module({
imports: [GroupesModule], // pour pouvoir injecter GroupesService
controllers: [StagiairesController],
providers: [StagiairesService],
})
export class StagiairesModule {}
📄 src/app.module.ts
import { Module } from '@nestjs/common';
import { GroupesModule } from './groupes/groupes.module';
import { StagiairesModule } from './stagiaires/stagiaires.module';
@Module({
imports: [GroupesModule, StagiairesModule],
})
export class AppModule {}
Avec le préfixe global api (leçon 3.3), voici les réponses réellement obtenues :
Réponses de l'API :
GET /api/groupes → 200 [{"code":"DEV101","filiere":"Développement digital"},{"code":"DEV102","filiere":"Développement digital"}]
GET /api/groupes/dev102 → 200 {"code":"DEV102","filiere":"Développement digital"}
GET /api/stagiaires/1 → 200 {"id":1,"nom":"Ben Ali","groupe":{"code":"DEV101","filiere":"Développement digital"}}
GET /api/stagiaires/7 → 404 {"message":"Stagiaire 7 introuvable","error":"Not Found","statusCode":404}
Observez : dev102 en minuscules fonctionne grâce à CodesService, utilisé à l'intérieur de GroupesModule ; le stagiaire 1 est renvoyé avec l'objet groupe complet ; l'exception NotFoundException levée dans le service devient automatiquement une réponse 404.
Lire l'erreur de dépendance
Supprimez imports: [GroupesModule] de StagiairesModule et relancez. L'application refuse de démarrer :
Erreur au démarrage :
Nest can't resolve dependencies of the StagiairesService (?). Please make sure that the argument GroupesService at index [0] is available in the StagiairesModule module.
Potential solutions:
- Is StagiairesModule a valid NestJS module?
- If GroupesService is a provider, is it part of the current StagiairesModule?
- If GroupesService is exported from a separate @Module, is that module imported within StagiairesModule?
@Module({
imports: [ /* the Module containing GroupesService */ ]
})
Ce message, que vous rencontrerez souvent, se lit ainsi :
StagiairesService (?): la classe qui n'a pas pu être créée ; le?marque le paramètre introuvable (s'il y en a plusieurs :(JournalService, ?)).GroupesService at index [0]: la dépendance manquante et sa position dans le constructeur.in the StagiairesModule module: le module dans lequel NestJS l'a cherchée.
Trois causes possibles, à vérifier dans l'ordre : le provider n'est déclaré nulle part ; il est déclaré dans un autre module qui ne l'exporte pas ; le module qui l'exporte n'est pas importé.
Erreur fréquente: ajouter GroupesService dans les providers de StagiairesModule pour « faire disparaître » l'erreur. L'application démarre… mais il existe alors deux instances de GroupesService avec chacune ses propres données, et CodesService manque à son tour. La bonne correction est toujours exports + imports.
Réexporter un module
Un module peut exporter un module qu'il importe. Ceux qui l'importent reçoivent alors aussi les exports du module réexporté :
@Module({
imports: [GroupesModule, FormationsModule],
exports: [GroupesModule, FormationsModule], // réexportation
})
export class ReferentielModule {}
// Ailleurs : un seul import donne accès à GroupesService ET FormationsService
@Module({ imports: [ReferentielModule], providers: [InscriptionsService] })
export class InscriptionsModule {}
Injecter dans la classe d'un module
La classe d'un module peut elle aussi recevoir des providers dans son constructeur, par exemple pour une configuration au démarrage :
@Module({ providers: [GroupesService, CodesService], exports: [GroupesService] })
export class GroupesModule {
constructor(private readonly groupesService: GroupesService) {
console.log(`GroupesModule : ${groupesService.findAll().length} groupes disponibles`);
}
}
Les classes de modules elles-mêmes ne peuvent pas être injectées comme providers (cela provoquerait des dépendances circulaires). Pour lancer du code au démarrage, on préfère les événements de cycle de vie (leçon 5.6).
Activités
Activité 1 Reproduire l'exemple
Dans api-formation, créez les modules groupes et stagiaires comme dans la leçon (fichiers identiques), créez requetes/stagiaires.http avec les quatre requêtes et vérifiez les réponses. Commit : feat: modules groupes et stagiaires.
Voir le corrigé
Les quatre réponses doivent être identiques à celles de la leçon. Si l'application ne démarre pas, lisez le message d'erreur en suivant la méthode de la leçon.
Activité 2 Provoquer et lire trois erreurs
Faites successivement les trois modifications suivantes (en annulant chaque fois la précédente), relevez le message et expliquez-le : (a) retirez exports: [GroupesService]; (b) retirez CodesService des providers de GroupesModule ; (c) dans StagiairesService, injectez CodesService en second paramètre.
Voir le corrigé
(a) Nest can't resolve dependencies of the StagiairesService (?)… argument GroupesService at index [0] is available in the StagiairesModule module: le module est importé mais n'exporte plus le service. (b) …dependencies of the GroupesService (?)… argument CodesService at index [0]… in the GroupesModule module: le service interne n'est plus déclaré. (c) …dependencies of the StagiairesService (GroupesService, ?)… argument CodesService at index [1]… in the StagiairesModule module: GroupesService est trouvé (il n'y a plus de ? en première position), mais CodesService est privé à GroupesModule. Pour (c), la bonne solution n'est pas d'exporter CodesService mais de passer par une méthode de GroupesService.
Activité 3 Ajouter un module formations
Créez un FormationsModule dont le service contient deux formations ({ id, intitule }). Chaque groupe reçoit un formationId, et GET /api/groupes/:code doit renvoyer l'intitulé de la formation. Quels imports et exports faut-il ajouter ?
Voir le corrigé
FormationsModule exporte FormationsService; GroupesModule importe FormationsModule et injecte FormationsService dans GroupesService. StagiairesModule n'a rien à changer : il ne dépend que de GroupesModule. La correction complète figure dans le TP 2.
Activité 4 Analyser une architecture
Pour chaque situation, dites si l'injection fonctionne :
- A importe B ; B exporte
BService; un service de A injecteBService. - A importe B ; B importe C ; C exporte
CService; un service de A injecteCService. - Même cas, mais B déclare aussi
exports: [C]. - B déclare
BServicedans ses providers sans l'exporter ; le contrôleur de B l'injecte.
Voir le corrigé
1 : oui. 2 : non, les imports ne sont pas transitifs : A ne voit que ce que B exporte. 3 : oui, B réexporte C. 4 : oui, un provider est toujours visible dans son propre module.
Activité 5 Deux instances ?
Ajoutez dans GroupesService un compteur de consultations incrémenté par findOne et une route GET /api/groupes/stats/consultations. Appelez plusieurs fois /api/groupes/dev101 puis /api/stagiaires/1: le compteur est-il partagé ? Ajoutez ensuite (à tort) GroupesService et CodesService aux providers de StagiairesModule et recommencez.
Voir le corrigé
Dans la bonne configuration, le compteur est partagé : une seule instance de GroupesService existe, utilisée par les deux modules. Avec la mauvaise, StagiairesModule crée sa propre instance : les consultations faites via /api/stagiaires ne sont plus comptées par la route des groupes. Avec une vraie base de données, l'effet serait moins visible mais les problèmes de cache ou de configuration seraient les mêmes.
À retenir
- controllers et providers déclarent ce que le module crée.
- Un provider est privé à son module ; exports le rend visible aux modules qui importent.
- imports donne accès aux exports d'autres modules ; les imports ne sont pas transitifs.
- « Nest can't resolve dependencies » : chercher la déclaration, l'export, puis l'import.
- Ne jamais redéclarer un provider d'un autre module : on créerait une seconde instance.