Pourquoi NestJS ?
Le framework Node.js TypeScript-first pour du backend scalable.
NestJS combine les meilleures idées d'Angular (DI, modules, décorateurs) avec la performance de Node.js. Il produit un code structuré, testable et maintenable — idéal pour les APIs REST, GraphQL et microservices.
Modules, providers, controllers — séparation des responsabilités claire et testable
IoC container puissant — les services sont injectés automatiquement
TypeORM, Passport, Swagger, Bull, GraphQL — tout intégré via packages officiels
TypeScript — bases indispensables
NestJS est écrit en TypeScript et tire parti de ses fonctionnalités avancées. Voici ce qu'il faut maîtriser.
Types, interfaces et types utilitaires
// Types stricts
const name: string = 'NestJS';
const port: number = 3000;
const active: boolean = true;
// Interface
interface User {
readonly id: number;
name: string;
email: string;
role?: 'admin' | 'user'; // union type + optionnel
}
// Types utilitaires
type CreateUserDto = Omit<User, 'id'>;
type UpdateUserDto = Partial<CreateUserDto>;
type UserSummary = Pick<User, 'id' | 'name'>;
// Type d'assertion
const input = document.getElementById('name') as HTMLInputElement;
// Non-null assertion
const value = maybeNull!.toString();
Classes TypeScript avec modificateurs
class UserService {
private readonly users: User[] = [];
constructor(
private readonly logger: Logger,
protected readonly config: ConfigService,
) {}
async findAll(): Promise<User[]> {
this.logger.log('Fetching all users');
return this.users;
}
async findOne(id: number): Promise<User | undefined> {
return this.users.find(u => u.id === id);
}
}
readonly pour les dépendances injectées dans le constructeur. C'est une bonne pratique qui indique clairement qu'elles ne seront pas réassignées.
Décorateurs TypeScript
Les décorateurs sont au cœur de NestJS. Ils annotent les classes et méthodes pour leur donner un comportement spécial.
Types de décorateurs
// 1. Décorateur de classe
@Controller('users')
@UseGuards(AuthGuard)
class UsersController { ... }
// 2. Décorateur de méthode
@Get(':id')
@HttpCode(200)
findOne(@Param('id') id: string) { ... }
// 3. Décorateur de propriété
@IsEmail()
@IsNotEmpty()
email: string;
// 4. Décorateur de paramètre
findOne(@Param('id') id: string, @Body() dto: CreateUserDto) { ... }
Créer un décorateur personnalisé
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
// Décorateur de paramètre personnalisé
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
// Usage dans un controller
@Get('me')
getMe(@CurrentUser() user: User) {
return user;
}
"experimentalDecorators": true et "emitDecoratorMetadata": true dans votre tsconfig — NestJS configure cela automatiquement.
Génériques (Generics)
Les génériques permettent d'écrire du code réutilisable et fortement typé.
// Réponse API générique
interface ApiResponse<T> {
data: T;
message: string;
statusCode: number;
timestamp: string;
}
// Pagination générique
interface PaginatedResult<T> {
items: T[];
total: number;
page: number;
limit: number;
totalPages: number;
}
// Service générique CRUD
abstract class CrudService<T, CreateDto, UpdateDto> {
abstract create(dto: CreateDto): Promise<T>;
abstract findAll(): Promise<T[]>;
abstract findOne(id: number): Promise<T>;
abstract update(id: number, dto: UpdateDto): Promise<T>;
abstract remove(id: number): Promise<void>;
}
// Fonction utilitaire générique
function paginate<T>(items: T[], page: number, limit: number): PaginatedResult<T> {
const start = (page - 1) * limit;
return {
items: items.slice(start, start + limit),
total: items.length,
page,
limit,
totalPages: Math.ceil(items.length / limit),
};
}
Installation & setup
Prérequis
node --version # v20+ recommandé
npm --version # v9+
Créer un projet NestJS
# Installer la CLI globalement
npm i -g @nestjs/cli
# Créer un nouveau projet
nest new mon-projet
# Choisir : npm (ou yarn/pnpm)
# Démarrer en mode développement
cd mon-projet
npm run start:dev
Scripts NPM disponibles
npm run start # Démarrage simple
npm run start:dev # Hot-reload (watch mode)
npm run start:debug # Avec debugger Node.js
npm run start:prod # Production (build + start)
npm run build # Compilation TypeScript
npm run test # Tests unitaires Jest
npm run test:e2e # Tests end-to-end
npm run test:cov # Coverage
Structure du projet
mon-projet/
├── src/
│ ├── app.controller.ts # Controller racine
│ ├── app.controller.spec.ts # Tests unitaires
│ ├── app.module.ts # Module racine
│ ├── app.service.ts # Service racine
│ └── main.ts # Point d'entrée
├── test/
│ ├── app.e2e-spec.ts # Tests E2E
│ └── jest-e2e.json
├── nest-cli.json # Config CLI NestJS
├── tsconfig.json # Config TypeScript
├── tsconfig.build.json # Config TS pour build
└── package.json
Structure recommandée par module
src/
├── users/
│ ├── dto/
│ │ ├── create-user.dto.ts
│ │ └── update-user.dto.ts
│ ├── entities/
│ │ └── user.entity.ts
│ ├── users.controller.ts
│ ├── users.controller.spec.ts
│ ├── users.module.ts
│ ├── users.service.ts
│ └── users.service.spec.ts
├── auth/
│ └── ...
├── app.module.ts
└── main.ts
nest g module users, nest g controller users, nest g service users pour générer la structure automatiquement.
Premier endpoint complet
main.ts — point d'entrée
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Validation globale
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // Supprime les champs non déclarés dans le DTO
forbidNonWhitelisted: true, // Erreur si champ inconnu
transform: true, // Convertit automatiquement les types
}));
// CORS
app.enableCors();
// Préfixe global
app.setGlobalPrefix('api/v1');
await app.listen(3000);
console.log('Application running on: http://localhost:3000');
}
bootstrap();
app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
@Module({
imports: [UsersModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Controller basique
import { Controller, Get, Post, Body, Param, Delete, Put, HttpCode, HttpStatus } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(+id);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
remove(@Param('id') id: string) {
return this.usersService.remove(+id);
}
}
Configuration avec @nestjs/config
npm install @nestjs/config
.env
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
JWT_SECRET=super-secret-key-change-in-production
PORT=3000
NODE_ENV=development
Intégration dans AppModule
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // Disponible dans toute l'app sans import
envFilePath: '.env',
}),
UsersModule,
],
})
export class AppModule {}
Utilisation du ConfigService
import { ConfigService } from '@nestjs/config';
@Injectable()
export class AppService {
constructor(private configService: ConfigService) {}
getDbUrl(): string {
return this.configService.get<string>('DATABASE_URL');
}
getPort(): number {
return this.configService.get<number>('PORT', 3000); // valeur par défaut
}
}
.env dans votre .gitignore. Utilisez un .env.example avec des valeurs fictives pour documenter les variables requises.