Patrons de Développement NestJS
Patrons NestJS de qualité production pour backends TypeScript modulaires.
Quand Activer
- Construire des API ou services NestJS
- Structurer des modules, contrôleurs et providers
- Ajouter une validation DTO, des guards, des intercepteurs ou des filtres d'exception
- Configurer des paramètres adaptés à l'environnement et des intégrations de base de données
- Tester des unités NestJS ou des points de terminaison HTTP
Structure du Projet
src/
├── app.module.ts
├── main.ts
├── common/
│ ├── filters/
│ ├── guards/
│ ├── interceptors/
│ └── pipes/
├── config/
│ ├── configuration.ts
│ └── validation.ts
├── modules/
│ ├── auth/
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── dto/
│ │ ├── guards/
│ │ └── strategies/
│ └── users/
│ ├── dto/
│ ├── entities/
│ ├── users.controller.ts
│ ├── users.module.ts
│ └── users.service.ts
└── prisma/ ou database/
- Gardez le code métier dans les modules fonctionnels.
- Placez les filtres transversaux, décorateurs, guards et intercepteurs dans
common/. - Conservez les DTO près du module propriétaire.
Bootstrap et Validation Globale
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
}),
);
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
- Activez toujours
whitelistetforbidNonWhitelistedsur les API publiques. - Préférez un seul pipe de validation global plutôt que de répéter la configuration de validation par route.
Modules, Contrôleurs et Providers
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':id')
getById(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.getById(id);
}
@Post()
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
}
@Injectable()
export class UsersService {
constructor(private readonly usersRepo: UsersRepository) {}
async create(dto: CreateUserDto) {
return this.usersRepo.create(dto);
}
}
- Les contrôleurs doivent rester légers : analyser l'entrée HTTP, appeler un provider, retourner des DTO de réponse.
- Placez la logique métier dans des services injectables, pas dans les contrôleurs.
- Exportez uniquement les providers dont les autres modules ont réellement besoin.
DTO et Validation
export class CreateUserDto {
@IsEmail()
email!: string;
@IsString()
@Length(2, 80)
name!: string;
@IsOptional()
@IsEnum(UserRole)
role?: UserRole;
}
- Validez chaque DTO de requête avec
class-validator. - Utilisez des DTO de réponse dédiés ou des sérialiseurs plutôt que de retourner directement les entités ORM.
- Évitez de divulguer des champs internes comme les hachages de mots de passe, jetons ou colonnes d'audit.
Authentification, Guards et Contexte de Requête
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('admin/report')
getAdminReport(@Req() req: AuthenticatedRequest) {
return this.reportService.getForUser(req.user.id);
}
- Gardez les stratégies d'auth et les guards locaux au module, sauf s'ils sont vraiment partagés.
- Encodez les règles d'accès grossières dans les guards, puis effectuez l'autorisation spécifique aux ressources dans les services.
- Préférez des types de requête explicites pour les objets de requête authentifiés.
Filtres d'Exception et Forme d'Erreur
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse<Response>();
const request = host.switchToHttp().getRequest<Request>();
if (exception instanceof HttpException) {
return response.status(exception.getStatus()).json({
path: request.url,
error: exception.getResponse(),
});
}
return response.status(500).json({
path: request.url,
error: 'Erreur interne du serveur',
});
}
}
- Conservez une enveloppe d'erreur cohérente dans toute l'API.
- Lancez des exceptions du framework pour les erreurs client attendues ; enregistrez et encapsulez les échecs inattendus de manière centralisée.
Configuration et Validation d'Environnement
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
validate: validateEnv,
});
- Validez les variables d'environnement au démarrage, pas paresseusement à la première requête.
- Accédez à la configuration via des helpers typés ou des services de configuration.
- Séparez les préoccupations dev/staging/prod dans des fabriques de configuration plutôt que de les disperser dans le code fonctionnel.
Persistance et Transactions
- Gardez le code ORM (repository) derrière des providers qui parlent le langage métier.
- Pour Prisma ou TypeORM, isolez les workflows transactionnels dans des services qui possèdent l'unité de travail.
- Ne laissez pas les contrôleurs coordonner des écritures multi-étapes directement.
Tests
describe('UsersController', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [UsersModule],
}).compile();
app = moduleRef.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
await app.init();
});
});
- Testez les providers en isolation avec des dépendances mockées.
- Ajoutez des tests au niveau requête pour les guards, pipes de validation et filtres d'exception.
- Réutilisez les mêmes pipes/filtres globaux dans les tests que ceux utilisés en production.
Paramètres Par Défaut pour la Production
- Activez la journalisation structurée et les identifiants de corrélation de requête.
- Terminez en cas de configuration ou variable d'environnement invalide plutôt que de démarrer partiellement.
- Préférez une initialisation asynchrone des providers pour les clients de base de données/cache avec des vérifications de santé explicites.
- Gardez les jobs d'arrière-plan et les consommateurs d'événements dans leurs propres modules, pas à l'intérieur des contrôleurs HTTP.
- Rendez le limitateur de débit, l'authentification et la journalisation d'audit explicites pour les points de terminaison publics.