Node.js Framework

NestJS

NestJS est un framework Node.js opinioné, inspiré d'Angular. Les modules, contrôleurs et providers offrent aux grandes équipes une architecture partagée et l'injection de dépendances nativement.

advanced16 min readUpdated 16 sept. 2026
cats.controller.ts
ts
// cats.controller.ts
import { Body, Controller, Get, Param, Post } from "@nestjs/common";
import { CatsService } from "./cats.service";
import { CreateCatDto } from "./dto/create-cat.dto";

@Controller("cats")
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  findAll() {
    return this.catsService.findAll();
  }

  @Get(":id")
  findOne(@Param("id") id: string) {
    return this.catsService.findOne(id);
  }

  @Post()
  create(@Body() createCatDto: CreateCatDto) {
    return this.catsService.create(createCatDto);
  }
}
Sortie
2017
S'exécute sur
Express ou Fastify
Langage
TypeScript
Style
Opinioné, modulaire
Idée centrale
DI et décorateurs
Version
11.x

Pourquoi c'est important

Pourquoi les équipes choisissent NestJS

Structure pour les grandes équipes

Les modules, les couches et les décorateurs offrent une carte commune à tous. Les nouveaux développeurs peuvent localiser une fonctionnalité sans avoir à lire l'intégralité de la base de code.

Injection de dépendances intégrée

Un véritable conteneur d'inversion de contrôle résout les constructeurs, gère les cycles de vie et rend chaque dépendance interchangeable lors des tests.

Préoccupations transversales codifiées

Les guards, interceptors, pipes et filtres gèrent l'authentification, le logging, la validation et les erreurs de manière déclarative, au niveau de la couche appropriée.

Le tableau complet

Modules, providers, contrôleurs

Un module définit la frontière d'une fonctionnalité, un provider contient la logique, et un contrôleur transforme les requêtes HTTP en appels de méthodes.

Modules

Frontière

Chaque fonctionnalité est un module qui déclare ce qu'il importe, fournit et exporte, rendant ainsi les dépendances explicites.

Providers

Injection

Les services, repositories et factories sont des providers que le conteneur instancie et injecte via le constructeur.

Contrôleurs

Routage

Des classes décorées mappent les méthodes et chemins HTTP vers des méthodes de gestion, avec des paramètres extraits par des décorateurs.

HTML5 en un coup d'oeil

La boîte à outils NestJS

Modules

@Module regroupe les contrôleurs et providers dans une frontière fonctionnelle.

Contrôleurs

@Controller et @Get transforment une classe en un ensemble de routes.

Providers

Les classes @Injectable sont résolues et partagées par le conteneur DI.

DTOs

Formes de requêtes typées validées par des pipes avant l'exécution du handler.

Guards

Décident si une requête peut continuer, en utilisant des rôles ou des politiques.

Interceptors

Enveloppent l'exécution du handler pour le logging, le caching et le mappage des réponses.

Le guide complet

NestJS: Tout ce que vous devez savoir

Qu’est-ce que NestJS ?

NestJS est un framework Node.js progressif qui apporte une structure au JavaScript côté serveur. Là où Express vous fournit des primitives et vous laisse le choix, NestJS vous impose une architecture : modules, controllers, providers, decorators et un conteneur d’injection de dépendances. Il s’inspire fortement d’Angular et des frameworks Java d’entreprise, tout en s’exécutant au-dessus d’Express ou de Fastify.

Cette approche directive (« opinionated ») constitue l’essentiel de sa proposition de valeur. Au sein d’une équipe de cinq personnes développant une API volumineuse, les problèmes les plus complexes sont rarement liés au protocole HTTP. Ils concernent plutôt le nommage, les frontières de modules, les tests et le maintien de patterns cohérents à mesure que la base de code s’étend. NestJS répond à ces enjeux via des conventions que vous pouvez soit accepter, soit remplacer, et il fournit toute l’infrastructure nécessaire — validation, guards, interceptors, configuration, utilitaires de test — pour vous éviter d’avoir à tout assembler vous-même.

Les modules définissent les frontières

Tout dans NestJS est organisé en modules. Un module est une classe décorée avec @Module qui déclare ses controllers, ses providers et ce qu’il exporte vers le reste de l’application.

@Module({
  imports: [DatabaseModule],
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}

imports permet d’importer d’autres modules dont vous avez besoin des providers exportés. providers sont les classes dont ce module est propriétaire. exports constitue l’API publique : seul ce qu’un module exporte peut être injecté ailleurs. Cela rend les dépendances explicites — si OrdersModule a besoin de CatsService, il doit importer CatsModule, et le conteneur (similaire à un compilateur) vous signalera lorsqu’il ne peut pas résoudre une dépendance.

Le AppModule racine importe chaque module de fonctionnalité. Les modules de fonctionnalité peuvent être regroupés en modules partagés ou core, et @Global() marque un module qui doit être disponible partout sans imports répétés. Utilisez les modules globaux avec parcimonie pour les infrastructures véritablement transversales, comme la configuration ou le logging.

Contrôleurs : le routage via les décorateurs

Un contrôleur mappe les requêtes HTTP vers des méthodes. Le décorateur de classe définit le chemin de base, tandis que les décorateurs de méthode définissent le verbe HTTP et le sous-chemin.

@Controller("cats")
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  findAll() {
    return this.catsService.findAll();
  }

  @Get(":id")
  findOne(@Param("id") id: string) {
    return this.catsService.findOne(id);
  }

  @Post()
  create(@Body() dto: CreateCatDto) {
    return this.catsService.create(dto);
  }
}

Les décorateurs de paramètres s’occupent de l’extraction : @Param, @Query, @Body, @Headers, @Req et @Res. Le gestionnaire (handler) retourne une valeur simple ou une promesse, et NestJS la sérialise en JSON avec le statut approprié. Si vous lancez une HttpException telle que NotFoundException, le framework la transformera en une réponse d’erreur appropriée.

Les contrôleurs doivent rester “fins” (thin). Ils traduisent une requête HTTP en un appel de méthode, et rien de plus. Toute la logique doit résider dans les providers, ce qui permet de garder le contrôleur testable et d’isoler les problématiques liées au protocole HTTP.

Providers et injection de dépendances

Un provider est toute classe pouvant être injectée. Marquez-la @Injectable() et déclarez-la dans un module, et le container s’occupe du reste.

@Injectable()
export class CatsService {
  constructor(private readonly db: DatabaseService) {}

  findAll() {
    return this.db.query("SELECT * FROM cats");
  }
}

L’injection s’effectue par constructeur et par type. Le container lit les métadonnées émises, résout chaque paramètre et injecte l’instance. Vous pouvez également injecter par token lorsque la valeur n’est pas une classe, en utilisant @Inject("CONFIG") avec un provider correspondant.

Les providers peuvent être personnalisés via des factories, des valeurs et des alias. Un provider de type factory est le moyen habituel d’injecter un élément nécessitant une configuration asynchrone, comme une connexion à une base de données :

@Module({
  providers: [
    {
      provide: "DATABASE",
      useFactory: async (config: ConfigService) => {
        return createPool(config.getOrThrow("DATABASE_URL"));
      },
      inject: [ConfigService],
    },
  ],
  exports: ["DATABASE"],
})
export class DatabaseModule {}

La portée (scope) est également importante. Le scope singleton par défaut partage une seule instance dans toute l’application, ce qui est idéal pour les services sans état. Le scope REQUEST crée une nouvelle instance par requête et est utile pour le contexte spécifique à une requête, mais cela impacte les performances car l’effet remonte tout le graphe de dépendances.

DTO, pipes et validation

La validation des entrées est une priorité absolue. Un DTO (data transfer object) est une classe qui décrit la structure d’une requête, décorée avec des règles class-validator.

export class CreateCatDto {
  @IsString()
  @MinLength(1)
  name: string;

  @IsInt()
  @Min(0)
  age: number;
}

Un pipe transforme ou valide la valeur avant qu’elle n’atteigne le handler. Activez le ValidationPipe intégré globalement et chaque DTO décoré sera vérifié automatiquement :

app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
  }),
);

whitelist supprime les propriétés inconnues, forbidNonWhitelisted les rejette purement et simplement, et transform convertit les payloads en véritables instances de DTO afin que les types soient réels au runtime. Une entrée invalide produit une 400 structurée sans qu’une seule ligne de code de validation ne soit nécessaire dans le handler. Les pipes peuvent également être appliqués par paramètre ou par contrôleur avec @UsePipes.

Guards, interceptors et filtres

Ces trois briques fondamentales couvrent la plupart des préoccupations transversales et s’exécutent dans un ordre fixe.

Les Guards s’exécutent avant le handler et décident si la requête peut continuer.

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>("roles", context.getHandler());
    if (!roles) return true;
    const { user } = context.switchToHttp().getRequest();
    return roles.includes(user?.role);
  }
}

Les Interceptors enveloppent l’appel au handler et peuvent transformer le résultat, mesurer le temps d’exécution ou ajouter du caching.

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const start = Date.now();
    return next.handle().pipe(
      tap(() => console.log(`took ${Date.now() - start}ms`)),
    );
  }
}

Les Exception filters capturent les erreurs levées et formatent la réponse. Sans filtre, NestJS utilise un comportement par défaut raisonnable ; un filtre personnalisé permet de garder une structure d’erreur cohérente sur l’ensemble de l’API.

@Catch(HttpException)
export class HttpErrorFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse();
    const status = exception.getStatus();
    response.status(status).json({
      statusCode: status,
      message: exception.message,
      timestamp: new Date().toISOString(),
    });
  }
}

Enregistrez-les globalement, par contrôleur ou par route. Les guards, interceptors et filtres globaux sont le moyen pour une application NestJS mature de centraliser l’authentification, l’observabilité et la gestion des erreurs.

Configuration et environnement

Le package @nestjs/config encapsule les variables d’environnement et les rend injectables. Importez-le une seule fois en tant que module global.

@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true })],
})
export class AppModule {}

Ensuite, injectez ConfigService partout où c’est nécessaire :

@Injectable()
export class DatabaseService {
  constructor(private readonly config: ConfigService) {}

  connect() {
    const url = this.config.getOrThrow<string>("DATABASE_URL");
    return createPool(url);
  }
}

Privilégiez getOrThrow pour les valeurs obligatoires afin qu’une variable manquante provoque une erreur au démarrage plutôt qu’à la première requête. Combinez cela avec un schéma de validation pour que la configuration soit vérifiée une seule fois, et gardez vos secrets totalement en dehors du dépôt.

Tests avec TestingModule

Les utilitaires de test de NestJS construisent un véritable conteneur en isolation. Vous ne déclarez que les providers à tester, puis vous remplacez leurs dépendances par des mocks.

describe("CatsController", () => {
  let controller: CatsController;
  let service: CatsService;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      controllers: [CatsController],
      providers: [CatsService],
    }).compile();

    controller = module.get(CatsController);
    service = module.get(CatsService);
  });

  it("returns all cats", () => {
    jest.spyOn(service, "findAll").mockReturnValue([]);
    expect(controller.findAll()).toEqual([]);
  });
});

Pour les tests de bout en bout (E2E), construisez l’application complète et utilisez supertest contre app.getHttpServer(). Comme le conteneur DI est le même que celui utilisé en production, ces tests sollicitent les guards, pipes et filters exactement comme ils le sont lors du déploiement.

Bonnes pratiques

  • Gardez vos contrôleurs légers et placez toute la logique dans des services injectables.
  • Définissez les limites de vos modules par fonctionnalité et n’exportez que ce dont les autres modules ont besoin.
  • Validez chaque requête à l’aide d’un DTO et d’un ValidationPipe global.
  • Utilisez des guards pour l’authentification et l’autorisation, et des interceptors pour formater les réponses.
  • Enregistrez des filtres d’exception globaux afin que toutes les réponses d’erreur partagent la même structure.
  • Injectez la configuration au lieu de lire directement process.env.
  • Privilégiez le scope singleton ; n’utilisez le scope request que lorsque c’est réellement nécessaire.
  • Testez avec TestingModule et remplacez les dépendances (override) plutôt que de solliciter les services réels.

Erreurs courantes

  • Construire des providers avec new au lieu de les injecter, ce qui casse le container.
  • Oublier d’ajouter un provider à un module, puis passer du temps à traquer une erreur “Nest can’t resolve dependencies”.
  • Importer un module mais oublier d’export le provider qu’il est censé partager.
  • Placer la logique métier dans les controllers, les rendant impossibles à tester unitairement.
  • Utiliser le request scope partout et dégrader silencieusement les performances.
  • Faire confiance à @Body() sans utiliser de DTO et de validation pipe.
  • Éparpiller le formatage des erreurs au lieu de le centraliser dans un filter.
  • Traiter les modules comme de simples dossiers plutôt que comme des frontières de dépendances.

Et après ?

NestJS est la méthode la plus structurée pour construire un backend Node.js, et il s’appuie directement sur Express ou Fastify. Consultez ces guides pour comprendre la couche HTTP que vous abstrayez. Une bonne maîtrise de TypeScript est essentielle car les décorateurs et l’injection de dépendances reposent sur les types, et les bases de Node.js vous permettront de comprendre l’environnement d’exécution sur lequel tout repose. Ensuite, développez un module de fonctionnalité de bout en bout : contrôleur, service, DTO et tests.

En pratique

Les quatre fichiers d'une fonctionnalité

Un contrôleur route, un service contient la logique, un module les lie ensemble, et un DTO définit et valide l'entrée.

cats.controller.ts
import {
  Body, Controller, Delete, Get, Param, Post,
} from "@nestjs/common";
import { CatsService } from "./cats.service";
import { CreateCatDto } from "./dto/create-cat.dto";

@Controller("cats")
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  findAll() {
    return this.catsService.findAll();
  }

  @Get(":id")
  findOne(@Param("id") id: string) {
    return this.catsService.findOne(id);
  }

  @Post()
  create(@Body() dto: CreateCatDto) {
    return this.catsService.create(dto);
  }

  @Delete(":id")
  remove(@Param("id") id: string) {
    return this.catsService.remove(id);
  }
}

Obtenir une dépendance

L'injection par constructeur permet au conteneur de créer et partager le service, et permet aux tests de substituer un mock. L'instancier soi-même annule l'intérêt du framework.

Préférer
@Controller("cats")
export class CatsController {
  constructor(private readonly catsService: CatsService) {}
}
Éviter
@Controller("cats")
export class CatsController {
  // Bypasses DI, breaks the lifecycle and
  // cannot be replaced in a test.
  private catsService = new CatsService();
}

Valider l'entrée

Un DTO avec des décorateurs class-validator associé à un ValidationPipe global rejette les requêtes invalides avant le handler et produit des réponses 400 cohérentes.

Préférer
export class CreateCatDto {
  @IsString()
  @MinLength(1)
  name: string;

  @IsInt()
  age: number;
}

@Post()
create(@Body() dto: CreateCatDto) {
  return this.catsService.create(dto);
}
Éviter
@Post()
create(@Body() body: any) {
  if (typeof body.name !== "string") {
    throw new BadRequestException();
  }
  // every handler repeats the same checks
}

Compromis

NestJS est-il le choix par défaut idéal ?

NestJS sacrifie une part de liberté au profit de la structure. Les conventions deviennent rentables à mesure que la base de code croît, mais peuvent sembler lourdes pour de petits projets.

Strengths

  • Structure dès le premier jour

    Les modules, contrôleurs et providers donnent une place à chaque fonctionnalité, permettant à une équipe en croissance de partager une architecture unique plutôt que d'en inventer une.

  • Injection de dépendances intégrée

    Un véritable conteneur gère les cycles de vie et rend chaque dépendance interchangeable, ce qui permet de garder les services testables sans état global.

  • Complet dès le départ

    La validation, les guards, les interceptors, la configuration et les utilitaires de test sont fournis avec le framework, vous évitant ainsi d'assembler trop de pièces manuellement.

Trade-offs

  • Beaucoup de cérémonie

    Les décorateurs, modules et DTOs ajoutent des fichiers et des concepts avant même que le premier endpoint ne fonctionne, rendant les petits services sur-ingéniés.

  • Adoption de ses opinions

    NestJS décide de la manière dont le code est organisé. Lutter contre ces conventions coûte plus cher que de choisir un framework plus léger.

  • TypeScript est indispensable

    Les décorateurs et l'injection de dépendances reposent sur les types et les métadonnées ; une équipe utilisant uniquement JavaScript fera face à une courbe d'apprentissage réelle.

FAQ

Foire aux questions

Keep learning

Related topics from the roadmap.

$ commencer à apprendre

Prêt à apprendre NestJS ?

Notre tutoriel interactif vous guide à travers NestJS pas à pas — avec des quiz et du vrai code que vous pouvez exécuter dans le navigateur.