Controllers & Providers
Controllers = couche HTTP · Providers = logique métier
La sĂ©paration entre controllers (routes) et providers (services) est fondamentale dans NestJS. Un controller ne fait que recevoir des requĂȘtes et dĂ©lĂ©guer au service appropriĂ©.
Controllers
Un controller gĂšre les requĂȘtes HTTP entrantes et retourne les rĂ©ponses.
Anatomie d'un controller
import {
Controller, Get, Post, Put, Delete, Patch,
Body, Param, Query, Headers, Req, Res,
HttpCode, HttpStatus, ParseIntPipe,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Controller('products') // Préfixe de route : /products
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
// GET /products
@Get()
findAll() {
return this.productsService.findAll();
}
// GET /products/featured
@Get('featured') // Route statique AVANT la route paramétrée
findFeatured() {
return this.productsService.findFeatured();
}
// GET /products/:id
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.productsService.findOne(id);
}
// POST /products
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body() createProductDto: CreateProductDto) {
return this.productsService.create(createProductDto);
}
// PUT /products/:id
@Put(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateProductDto: UpdateProductDto,
) {
return this.productsService.update(id, updateProductDto);
}
// DELETE /products/:id
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
remove(@Param('id', ParseIntPipe) id: number) {
return this.productsService.remove(id);
}
}
Routes avancées
Wildcards et patterns
// Wildcard
@Get('ab*cd')
findAll() { ... } // Correspond Ă : abcd, ab_cd, abanythingcd
// Préfixe de version
@Controller({ path: 'users', version: '1' })
class UsersControllerV1 { ... }
// Routes imbriquées avec plusieurs contrÎleurs
@Controller('orders/:orderId/items')
class OrderItemsController {
@Get()
findAll(@Param('orderId') orderId: string) { ... }
}
Redirection et streaming
// Redirection
@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs() {}
// Redirection dynamique
@Get('redirect')
redirect() {
return { url: 'https://nestjs.com', statusCode: 301 };
}
// Réponse brute avec @Res (déconseillé en général)
@Get('stream')
streamFile(@Res() res: Response) {
const file = createReadStream('/path/to/file.pdf');
res.set({ 'Content-Type': 'application/pdf' });
file.pipe(res);
}
Décorateurs HTTP
Tableau des décorateurs principaux
| Décorateur | RÎle |
|---|---|
| @Get() | Route GET |
| @Post() | Route POST |
| @Put() / @Patch() | Mise Ă jour complĂšte / partielle |
| @Delete() | Suppression |
| @HttpCode(201) | Code HTTP de retour personnalisé |
| @Header('key','val') | Ajoute un header à la réponse |
| @Redirect(url, code) | Redirige vers une URL |
Codes HTTP avec HttpStatus
import { HttpStatus } from '@nestjs/common';
@Post()
@HttpCode(HttpStatus.CREATED) // 201
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT) // 204
// Lancer une exception HTTP
throw new HttpException('Not Found', HttpStatus.NOT_FOUND);
// Ou directement :
throw new NotFoundException('User not found');
throw new BadRequestException('Invalid data');
throw new UnauthorizedException();
Params, Query, Body & Headers
@Controller('search')
export class SearchController {
// Params de route : /search/users/42
@Get(':resource/:id')
findOne(
@Param('resource') resource: string,
@Param('id', ParseIntPipe) id: number,
) { ... }
// Query string : /search?q=nestjs&limit=10&page=2
@Get()
search(
@Query('q') query: string,
@Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
) { ... }
// Body JSON
@Post()
create(@Body() dto: CreateSearchDto) { ... }
// Body partiel
@Patch(':id')
update(@Param('id') id: string, @Body('name') name: string) { ... }
// Headers
@Get('secure')
secure(@Headers('authorization') auth: string) { ... }
// Objet request complet (Express)
@Get('full')
full(@Req() req: Request) {
return req.ip;
}
}
ParseIntPipe : Transforme automatiquement le paramÚtre de route (string) en number et lance une BadRequestException si la conversion échoue.
Providers
Un provider est n'importe quelle classe annotée avec @Injectable(). NestJS peut l'injecter automatiquement dans d'autres classes.
Service classique
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly usersRepository: Repository<User>,
) {}
async create(dto: CreateUserDto): Promise<User> {
const user = this.usersRepository.create(dto);
return this.usersRepository.save(user);
}
async findAll(): Promise<User[]> {
return this.usersRepository.find();
}
async findOne(id: number): Promise<User> {
const user = await this.usersRepository.findOneBy({ id });
if (!user) throw new NotFoundException(`User #${id} not found`);
return user;
}
async remove(id: number): Promise<void> {
await this.findOne(id); // Vérifie l'existence
await this.usersRepository.delete(id);
}
}
Types de providers
// 1. Class provider (le plus courant)
providers: [UsersService]
// 2. Value provider
providers: [{ provide: 'CONFIG', useValue: { apiKey: '...' } }]
// 3. Factory provider
providers: [{
provide: 'DATABASE',
useFactory: (config: ConfigService) => new Database(config.get('DB_URL')),
inject: [ConfigService],
}]
// 4. Alias provider
providers: [{ provide: IUserService, useClass: UsersService }]
Injection de dépendances
// Injection par constructeur (recommandé)
@Injectable()
export class OrdersService {
constructor(
private readonly usersService: UsersService,
private readonly productsService: ProductsService,
private readonly mailerService: MailerService,
) {}
}
// Injection de valeur
@Injectable()
export class AppService {
constructor(
@Inject('CONFIG') private config: AppConfig,
) {}
}
// Injection optionnelle
@Injectable()
export class LoggerService {
constructor(
@Optional() @Inject('LOGGER_OPTIONS') private options?: LoggerOptions,
) {}
}
Scope des providers
import { Injectable, Scope } from '@nestjs/common';
// DEFAULT : singleton (une seule instance pour toute l'app)
@Injectable()
export class AppService { ... }
// REQUEST : nouvelle instance par requĂȘte
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
constructor(@Inject(REQUEST) private request: Request) {}
}
// TRANSIENT : nouvelle instance Ă chaque injection
@Injectable({ scope: Scope.TRANSIENT })
export class TransientService { ... }
Scope par défaut = singleton. Utilisez
REQUEST seulement si vous avez besoin d'accĂ©der aux donnĂ©es de la requĂȘte. Le scope REQUEST a un impact sur les performances car il crĂ©e une nouvelle instance Ă chaque requĂȘte.