Pourquoi API REST avec NestJS ?
API REST est un modèle d'architecture pour les applications web qui utilise l'HTTP pour communiquer entre clients et serveurs. Avec NestJS, vous pouvez créer des API REST de manière plus rapide et efficace en utilisant TypeScript. Un dev a besoin de ce type d'API pour interagir avec des données sur le backend et mettre à jour ces données dans une base de données.
Un cas concret est l'application de gestionnaire de tâches où vous avez besoin d'un système qui permet aux utilisateurs de créer, lire, mettre à jour et supprimer des tâches.
Prerequis
- Connaissances en JavaScript/TypeScript
- Compréhension de base de la programmation orientée objet
- Node.js installé sur votre machine (version recommandée : 14.x)
- npm ou yarn pour la gestion des dépendances
Concepts fondamentaux
Controller
Un controller gère les requêtes HTTP et retourne une réponse. Il définit les routes de l'API.
// app.controller.ts
import { Controller, Get, Post } from '@nestjs/common';
@Controller('tasks')
export class TasksController {
@Get()
findAll() {
return 'This action returns all tasks';
}
@Post()
create() {
return 'This action adds a new task';
}
}
Service
Un service contient la logique métier de l'application. Il est utilisé par les controllers.
// tasks.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class TasksService {
findAll() {
return 'This action returns all tasks';
}
create(task: any) {
return 'This action adds a new task';
}
}
Module
Un module regroupe les contrôleurs, les services et autres éléments logiques liés.
// app.module.ts
import { Module } from '@nestjs/common';
import { TasksController } from './app.controller';
import { TasksService } from './tasks.service';
@Module({
imports: [],
controllers: [TasksController],
providers: [TasksService],
})
export class AppModule {}
DTO (Data Transfer Object)
Un DTO est utilisé pour définir la structure des données d'entrée et de sortie.
// create-task.dto.ts
import { IsNotEmpty } from 'class-validator';
export class CreateTaskDto {
@IsNotEmpty()
title: string;
description?: string;
}
Validation
NestJS utilise le validateur class-validator pour la validation des données.
// tasks.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { TasksService } from './tasks.service';
import { CreateTaskDto } from './create-task.dto';
@Controller('tasks')
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
@Post()
async create(@Body() createTaskDto: CreateTaskDto) {
return this.tasksService.create(createTaskDto);
}
}
Middleware
Un middleware est une fonction qui est appelée avant que le controller ne traite la requête.
// logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
}
}
Exception Filter
Un exception filter permet de gérer les exceptions et de renvoyer une réponse personnalisée.
// http.exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
Mise en pratique : projet fil rouge
Nous allons créer un gestionnaire de tâches simple avec NestJS.
Étape 1 : Initialisation du projet
npm install -g @nestjs/cli
nest new task-manager
cd task-manager
Étape 2 : Création d'un service et d'un controller
## tasks.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class TasksService {
private tasks: any[] = [];
findAll() {
return this.tasks;
}
create(task: any) {
this.tasks.push(task);
return task;
}
}
typescript
## app.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { TasksService } from './tasks.service';
@Controller('tasks')
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
@Get()
findAll() {
return this.tasksService.findAll();
}
@Post()
create(@Body() taskDto: any) {
return this.tasksService.create(taskDto);
}
}
Étape 3 : Ajout de la validation avec DTO
## create-task.dto.ts
import { IsNotEmpty, IsOptional } from 'class-validator';
export class CreateTaskDto {
@IsNotEmpty()
title: string;
@IsOptional()
description?: string;
}
Mise à jour du controller :
## app.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { TasksService } from './tasks.service';
import { CreateTaskDto } from './create-task.dto';
@Controller('tasks')
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
@Get()
findAll() {
return this.tasksService.findAll();
}
@Post()
create(@Body() taskDto: CreateTaskDto) {
return this.tasksService.create(taskDto);
}
}
Étape 4 : Ajout de middleware et filter
Création du middleware :
## logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
}
}
Ajout du middleware au module principal :
## app.module.ts
import { Module } from '@nestjs/common';
import { TasksController } from './app.controller';
import { TasksService } from './tasks.service';
import { LoggerMiddleware } from './logger.middleware';
@Module({
imports: [],
controllers: [TasksController],
providers: [TasksService, LoggerMiddleware],
})
export class AppModule {}
Création du filter :
## http.exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
Ajout du filter au module principal :
## app.module.ts
import { Module } from '@nestjs/common';
import { TasksController } from './app.controller';
import { TasksService } from './tasks.service';
import { LoggerMiddleware } from './logger.middleware';
import { HttpExceptionFilter } from './http.exception.filter';
@Module({
imports: [],
controllers: [TasksController],
providers: [TasksService, LoggerMiddleware, HttpExceptionFilter],
})
export class AppModule {}
Étape 5 : Exécution et test de l'API
npm run start
Test avec Postman ou curl :
curl -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d '{"title": "Task 1", "description": "Description 1"}'
curl http://localhost:3000/tasks
Erreurs frequentes et debugging
Erreur 1 : Cannot find module 'class-validator'
## ❌ Mauvais
import { IsNotEmpty } from 'class-validator';
## ✅ Correct
npm install class-validator --save
import { IsNotEmpty } from 'class-validator';
Erreur 2 : TypeError: Cannot read property 'apply' of undefined
## ❌ Mauvais
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req, res, next) {
console.log(`Request...`);
next();
}
}
## ✅ Correct
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
}
}
Erreur 3 : Unhandled exception
## ❌ Mauvais
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
## ✅ Correct
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
Pour aller plus loin
Piste 1 : Ajout d'une base de données
Utilisez TypeORM pour connecter votre application à une base de données.
- Installez TypeORM et les dépendances requises.
- Configurez la connexion à la base de données.
- Mettez à jour le service pour utiliser des entités et des repositories.
Piste 2 : Création d'une API sécurisée
Ajoutez l'authentification JWT pour sécuriser votre API.
- Installez les dépendances nécessaires (jsonwebtoken).
- Créez un middleware d'authentification.
- Appliquez le middleware aux routes sensibles.
Piste 3 : Création d'une API de blog
Créez une API simple pour gérer des articles.
- Ajoutez une ressource 'articles'.
- Implémentez les opérations CRUD (Create, Read, Update, Delete).
- Testez l'API avec Postman ou curl.
Défi pratique : Création d'un CLI tool
Créez un CLI tool utilisant NestJS pour gérer des tâches en ligne de commande.
- Installez le package
@nestjs/cliglobal. - Créez un projet CLI.
- Implémentez les commandes (create, read, update, delete).
- Testez le CLI avec npm scripts ou directement dans la console.