Guards, interceptors et exception filters
Guard, pipe, interceptor, filter dans le flux d’une requête.
Objectifs
À la fin de cette leçon, vous saurez :
- situer guards, pipes, interceptors et filters dans le flux ;
- écrire un guard de rôles ;
- reconnaître chaque mécanisme dans Devmind.
🔗 Pour vous rafraîchir la mémoire : décorateurs et metadata · les bases TypeScript · modules : import/export · streams et pipe()
La chaîne complète d'une requête Nest
Middleware → Guard → Interceptor(avant) → Pipe → Handler
↓
Interceptor(après) ← Exception Filter ← (exception ?) ← retour
| Mécanisme | Rôle | Votre équivalent vanilla |
|---|---|---|
| Guard | peut-il entrer ? (auth, rôle) | votre SessionGuard + RolesGuard |
| Pipe | transformer/valider les données | ParseIntPipe, ValidationPipe |
| Interceptor | croiser la requête/réponse (logs, cache) | rien d'équivalent encore |
| Filter | traduire exceptions en réponses | vos try/catch globaux |
Un guard de rôles
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requis = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requis) return true; // route publique
const { user } = context.switchToHttp().getRequest();
if (!user) throw new UnauthorizedException();
if (!requis.includes(user.role)) throw new ForbiddenException('Rôle insuffisant');
return true;
}
}
// utilisation :
@Roles('ADMIN')
@Post()
creer(@Body() dto: CreateCourseDto) { ... }
C'est — littéralement — le RolesGuard du projet Devmind : @Roles('ADMIN') sur le contrôleur admin, lecture des métadonnées par reflection (chapitre 34), 401 sans identité / 403 avec identité insuffisante.
L'exception filter : traduction centralisée
@Catch()
export class ToutesExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const reponse = host.switchToHttp().getResponse();
const statut = exception instanceof HttpException
? exception.getStatus() : 500;
reponse.status(statut).json({
statusCode: statut,
message: exception instanceof HttpException
? exception.message : 'Erreur interne',
});
}
}
Une seule porte de sortie pour toutes les erreurs : format uniforme, 500 réservé aux surprises, logs centralisés. Vos try/catch dispersés deviennent obsolètes.
Un interceptor, pour de vrai
Le logging global est LE cas d'école :
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from "@nestjs/common";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req = context.switchToHttp().getRequest();
const debut = Date.now();
console.log(`→ ${req.method} ${req.url}`);
return next.handle().pipe(
tap(() => console.log(`← ${req.method} ${req.url} (${Date.now() - debut} ms)`)),
);
}
}
À lire comme le schéma de flux : next.handle() représente « la suite de la chaîne jusqu'à la réponse ». Avant : notre log d'entrée. Après (via tap, qui observe sans modifier) : le log de sortie avec la durée. Enregistré globalement (app.useGlobalInterceptors(new LoggingInterceptor())), il trace chaque requête — le request-id du chapitre 41 se brancherait exactement ici.
Exercice
- Dans Devmind, retrouvez ces quatre mécanismes et leurs usages réels.
- Écrivez un interceptor qui logge durée et statut de chaque requête.
- Pourquoi le guard s'exécute-t-il AVANT le pipe ?
Résumé
- Guard = autorisation ; Pipe = validation ; Interceptor = traversée ; Filter = traduction.
- Chaque mécanisme = un cross-cutting concern sorti des handlers.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
Question 3. Inutile de valider une requête que l'on va refuser : l'autorisation est moins coûteuse et évite de faire travailler le pipeline pour rien. Sécurité d'abord, données ensuite — même ordre que votre SessionGuard global avant tout handler.