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.

🏗️
Architecture modulaire

Modules, providers, controllers — séparation des responsabilités claire et testable

💉
Injection de dépendances

IoC container puissant — les services sont injectés automatiquement

🔧
Écosystème riche

TypeORM, Passport, Swagger, Bull, GraphQL — tout intégré via packages officiels

NestJS 10+ TypeScript 5 Node.js 20+ Express / Fastify

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);
  }
}
Convention NestJS : Utilisez toujours 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;
}
tsconfig.json requis : Les décorateurs nécessitent "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
Générateur CLI : Utilisez 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
  }
}
Ne jamais committer le .env ! Ajoutez .env dans votre .gitignore. Utilisez un .env.example avec des valeurs fictives pour documenter les variables requises.
✏️ Exercices du module ▶ Mini-projet Module 02 →