Polar Code 🎭

Command Palette

Search for a command to run...

02
Pièce N°02

Chapitre 2 : Injection de Dépendances – Le Réseau Fantôme

La pluie cognaît contre les vitres du poste. Sur le tableau, des fils rouges relient des photos, des notes, des noms. Chaque fil raconte une histoire. Chaque connexion cache un secret. L'Injection de Dépendances dans NestJS, c'est ce tableau. Un réseau souterrain où les services se parlent sans jamais se toucher. Un système de tuyauteries invisibles où coule la logique de votre application.


2.1. Le Contact : Ce que vous croyez savoir

Vous pensez comprendre. Un service A a besoin d'un service B. Vous l'importez. Vous l'instanciez. Simple.

// L'ancienne manière. La manière dangereuse.
class UsersController {
  private usersService: UsersService;
  
  constructor() {
    this.usersService = new UsersService(); // Vous tenez l'arme.
  }
}

Problème n°1 : Vous êtes maintenant marié à UsersService. Pour le tester, vous devez tricher avec ses dépendances internes. Pour le changer, vous devez retoucher chaque contrôleur qui l'appelle. C'est comme si chaque inspecteur devait fabriquer son propre arme de service.

Problème n°2 : Et si UsersService a besoin d'un DatabaseService lui-même dépendant d'un ConfigService ? Vous voilà à empiler des new comme des cercueils dans un fourgon. L'application démarre et c'est l'effondrement.


2.2. Le Témoin Silencieux : Le Conteneur IoC

NestJS ne fonctionne pas comme ça. Il a un Conteneur IoC (Inversion of Control). Traduction : c'est le grand ordonnateur. Le cerveau qui sait qui dépend de qui.

Quand vous écrivez :

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
  // Pas de 'new'. Juste une déclaration.
}

Voici ce qui se passe dans l'ombre :

  1. La Demande : NestJS voit le paramètre usersService de type UsersService.
  2. L'Enquête : Il va chercher dans ses registres. Qui est UsersService ? Où vit-il ? Est-il déjà créé ?
  3. La Fabrication : Si UsersService n'existe pas, NestJS le crée. Mais attention : il regarde d'abord son constructeur. UsersService a besoin d'un DatabaseService ? NestJS crée d'abord celui-là. C'est une enquête récursive.
  4. La Livraison : Il injecte l'instance terminée dans votre contrôleur. Propre. Invisible.

C'est ça, l'Injection de Dépendances : Demander, ne pas prendre.


2.3. Les Règles du Jeu : Les Fournisseurs (Providers)

Un Provider dans NestJS, c'est n'importe quoi que vous pouvez injecter. Un service, un repository, une factory, une valeur. Tout est défini dans le tableau providers d'un module.

@Module({
  providers: [
    UsersService, // Le classique
    DatabaseService,
    { provide: 'API_KEY', useValue: process.env.API_KEY }, // Une valeur simple
    { provide: 'CONNECTION', useFactory: async () => { // Une factory
      return await createConnection();
    }},
    { provide: AbstractService, useClass: ConcreteService } // Une abstraction
  ]
})
export class AppModule {}

Le Token : Le provide: est la clé. Habituellement, c'est le nom de la classe (UsersService). Mais ça peut être une chaîne ('API_KEY') ou un symbole. C'est le nom sous lequel on demandera la dépendance.

Le use* : C'est la recette de fabrication.

  • useClass : Instancie cette classe (le cas le plus courant).
  • useValue : Utilise cette valeur telle quelle.
  • useFactory : Appelle cette fonction pour créer la dépendance.
  • useExisting : Alias vers un autre provider déjà défini.

2.4. Les Portes du Réseau : Les Scopes d'Injection

Toutes les dépendances ne vivent pas aussi longtemps. NestJS gère leur durée de vie :

  • SINGLETON (par défaut) : Une seule instance pour toute l'app. Tous les contrôleurs partagent le même service. Comme le registre central du poste de police. Économique, mais état partagé.
  • REQUEST : Une nouvelle instance est créée pour chaque requête HTTP. L'instance est détruite à la fin de la requête. Comme un dossier d'enquête ouvert et fermé pour chaque affaire. Isolé, mais coûteux.
  • TRANSIENT : Une nouvelle instance à chaque injection. Même deux services dans la même requête auront des instances différentes.

Pour changer la portée :

@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
  // Né et meurt avec chaque requête
}

2.5. Les Passages Secrets : L'Injection Personnalisée

Parfois, la dépendance n'est pas une classe. Parfois vous voulez injecter un objet de configuration, une librairie externe.

Injecter par clé :

constructor(
  @Inject('API_KEY') private readonly apiKey: string,
  @Inject('HTTP_CLIENT') private readonly httpClient: any
) {}

Le Pattern de Configuration (un classique) :

// config.service.ts
@Injectable()
export class ConfigService {
  constructor(@Inject('CONFIG') private readonly config: any) {}
  
  get databaseUrl(): string {
    return this.config.database.url;
  }
}

// app.module.ts
@Module({
  providers: [
    ConfigService,
    {
      provide: 'CONFIG',
      useFactory: () => yaml.load(fs.readFileSync('config.yaml', 'utf8'))
    }
  ]
})

2.6. La Pièce Maîtresse : L'Injection dans les Tests

C'est là que tout s'éclaire. Pour tester un contrôleur en isolation, vous ne voulez pas d'un vrai UsersService. Vous voulez un faux. Un témoin coopératif.

// Dans votre test
const mockUsersService = {
  findUser: jest.fn().mockReturnValue({ id: '1', name: 'Test' })
};

beforeEach(async () => {
  const module = await Test.createTestingModule({
    controllers: [UsersController],
  })
  .overrideProvider(UsersService) // On intercepte la demande
  .useValue(mockUsersService)     // On répond avec notre faux
  .compile();

  controller = module.get(UsersController);
});

Le réseau souterrain est temporairement détourné. Votre contrôleur reçoit le mock sans le savoir. Vous testez son comportement, pas celui du vrai service.


2.7. Le Cadavre dans le Placard : Les Erreurs Courantes

  1. Dépendance Circulaire : A dépend de B qui dépend de A. NestJS le détectera avec une erreur claire. Solution : refactor, ou utiliser forwardRef(() => Service) en dernier recours.

  2. Provider Non Enregistré : Vous demandez UsersService dans un module où il n'est pas dans providers. Ou il n'est pas exporté depuis son module d'origine. L'erreur : Nest can't resolve dependencies...

  3. Scope Mismatch : Injecter un service REQUEST dans un service SINGLETON. Impossible. Le singleton vit plus longtemps. NestJS vous arrêtera.


Épilogue du Chapitre 2

L'Injection de Dépendances n'est pas un détail technique. C'est l'architecture nerveuse de votre application. Chaque constructor(private service: Service) est un fil tendu dans l'ombre. Ces fils ne s'emmêlent pas parce que NestJS joue le chef d'orchestre.

Vous ne créez plus. Vous déclarez un besoin. Vous ne couplez plus. Vous découplez. Vous ne testez plus avec difficulté. Vous mocker avec élégance.

Dans les chapitres sombres qui viennent, nous verrons comment ces services, une fois proprement injectés, traitent les données, interrogent les bases, et authentifient les requêtes. Mais souvenez-vous : tout repose sur ce réseau fantôme.

La prochaine fois que vous verrez un décorateur @Injectable(), souvenez-vous qu'il ne marque pas une classe. Il marque un témoin potentiel. Prêt à être appelé à la barre, quand son heure viendra.