Pourquoi Microservices avec NestJS ?
Dans un monde où les applications croissent rapidement en taille et en complexité, il est devenu nécessaire d'organiser le code en services plus petits et plus indépendants. Les microservices offrent cette possibilité, permettant à une application de se développer et évoluer de manière modulaire et échelonnable.
Un cas concret : imaginez une application e-commerce qui nécessite des services distincts pour la gestion des commandes, les paiements, le stockage des produits et l'authentification des utilisateurs. Chaque service peut être développé, testé et déployé indépendamment, ce qui accélère le développement global.
Prerequis
Connaissances nécessaires :
- Langage TypeScript
- Framework NestJS
- Concept de microservices
- Connaissance des bases de données (SQL/NoSQL)
- Compréhension des API RESTful
- Familiarité avec les outils de versionnement (Git)
Outils à installer :
- Node.js v14.x ou ultérieur
- npm v6.x ou ultérieur
- NestJS CLI
Concepts fondamentaux
Concept 1: Création d'un Microservice avec NestJS
La première étape pour créer un microservice est de générer un nouveau projet NestJS en utilisant le CLI.
nest new my-microservice
Cela crée une structure de base du projet, incluant des fichiers comme app.module.ts, main.ts et d'autres fichiers dépendants.
Concept 2: Communication entre Microservices
Les microservices communiquent généralement via des canaux de messagerie, tels que RabbitMQ ou Kafka. NestJS offre un module pour faciliter cette communication.
npm install --save @nestjs/microservices
Ajoutez le module au fichier app.module.ts :
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
@Module({
imports: [
ClientsModule.register([
{
name: 'PRODUCT_SERVICE',
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'products_queue',
queueOptions: { durable: false },
},
},
]),
],
})
export class AppModule {}
Concept 3: Définition d'Interfaces et DTO
Les Data Transfer Objects (DTO) sont utilisés pour définir les structures des données en entrée et sortie.
// product.dto.ts
export class CreateProductDto {
name: string;
price: number;
}
export class UpdateProductDto {
name?: string;
price?: number;
}
Concept 4: Définition de Services et Controllers
Les services contiennent la logique métier, tandis que les contrôleurs gèrent les requêtes HTTP.
// product.service.ts
import { Injectable } from '@nestjs/common';
import { CreateProductDto, UpdateProductDto } from './product.dto';
@Injectable()
export class ProductService {
private products = [];
create(createProductDto: CreateProductDto) {
this.products.push({ ...createProductDto, id: Math.random().toString(36).substring(2, 9) });
return this.products;
}
update(id: string, updateProductDto: UpdateProductDto) {
const productIndex = this.products.findIndex(p => p.id === id);
if (productIndex !== -1) {
this.products[productIndex] = { ...this.products[productIndex], ...updateProductDto };
}
return this.products;
}
findAll() {
return this.products;
}
findOne(id: string) {
return this.products.find(p => p.id === id);
}
}
typescript
// product.controller.ts
import { Controller, Post, Get, Put, Delete, Param, Body } from '@nestjs/common';
import { ProductService } from './product.service';
import { CreateProductDto, UpdateProductDto } from './product.dto';
@Controller('products')
export class ProductController {
constructor(private readonly productService: ProductService) {}
@Post()
create(@Body() createProductDto: CreateProductDto) {
return this.productService.create(createProductDto);
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.productService.findOne(id);
}
@Put(':id')
update(@Param('id') id: string, @Body() updateProductDto: UpdateProductDto) {
return this.productService.update(id, updateProductDto);
}
@Get()
findAll() {
return this.productService.findAll();
}
}
Mise en pratique : Projet fil rouge
Étape 1 : Création du Projet
Créez un nouveau projet NestJS :
nest new product-service
cd product-service
Étape 2 : Configuration de la Communication avec RabbitMQ
Ajoutez le module ClientsModule pour configurer RabbitMQ.
// app.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
@Module({
imports: [
ClientsModule.register([
{
name: 'PRODUCT_SERVICE',
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'products_queue',
queueOptions: { durable: false },
},
},
]),
],
})
export class AppModule {}
Étape 3 : Création des DTO
Créez les fichiers product.dto.ts :
// product.dto.ts
export class CreateProductDto {
name: string;
price: number;
}
export class UpdateProductDto {
name?: string;
price?: number;
}
Étape 4 : Création des Services et Controllers
Créez les fichiers product.service.ts et product.controller.ts :
// product.service.ts
import { Injectable } from '@nestjs/common';
import { CreateProductDto, UpdateProductDto } from './product.dto';
@Injectable()
export class ProductService {
private products = [];
create(createProductDto: CreateProductDto) {
this.products.push({ ...createProductDto, id: Math.random().toString(36).substring(2, 9) });
return this.products;
}
update(id: string, updateProductDto: UpdateProductDto) {
const productIndex = this.products.findIndex(p => p.id === id);
if (productIndex !== -1) {
this.products[productIndex] = { ...this.products[productIndex], ...updateProductDto };
}
return this.products;
}
findAll() {
return this.products;
}
findOne(id: string) {
return this.products.find(p => p.id === id);
}
}
typescript
// product.controller.ts
import { Controller, Post, Get, Put, Delete, Param, Body } from '@nestjs/common';
import { ProductService } from './product.service';
import { CreateProductDto, UpdateProductDto } from './product.dto';
@Controller('products')
export class ProductController {
constructor(private readonly productService: ProductService) {}
@Post()
create(@Body() createProductDto: CreateProductDto) {
return this.productService.create(createProductDto);
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.productService.findOne(id);
}
@Put(':id')
update(@Param('id') id: string, @Body() updateProductDto: UpdateProductDto) {
return this.productService.update(id, updateProductDto);
}
@Get()
findAll() {
return this.productService.findAll();
}
}
Étape 5 : Configuration du Service
Ajoutez le service product.service.ts au fichier app.module.ts :
// app.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { ProductController } from './product.controller';
import { ProductService } from './product.service';
@Module({
imports: [
ClientsModule.register([
{
name: 'PRODUCT_SERVICE',
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'products_queue',
queueOptions: { durable: false },
},
},
]),
],
controllers: [ProductController],
providers: [ProductService],
})
export class AppModule {}
Étape 6 : Exécution du Service
Exécutez le service :
npm run start
Erreurs frequentes et debugging
Erreur 1 : TypeError: Cannot read property 'id' of undefined
Cela peut se produire si vous essayez d'accéder à une propriété sur un objet qui est undefined.
// ❌ Mauvais
const product = req.body;
const productId = product.id;
// ✅ Correct
const product = req.body;
if (product && product.id) {
const productId = product.id;
}
Erreur 2 : Module not found: Can't resolve '@nestjs/microservices'
Assurez-vous d'avoir installé le module correctement.
npm install --save @nestjs/microservices
Erreur 3 : RabbitMQ connection failed
Vérifiez que RabbitMQ est en cours d'exécution et que les URL sont correctes.
Pour aller plus loin
- Apprendre à utiliser des bases de données NoSQL avec NestJS
- Intégrer des services externes (ex: Stripe pour les paiements)
- Utiliser le pattern CQRS (Command Query Responsibility Segregation)
Défi pratique
Créez un microservice pour gérer les utilisateurs. Implémentez les fonctionnalités CRUD et configurez la communication avec RabbitMQ.