Introduction to NestJS Microservices
Microservices architecture has gained immense popularity in recent years due to its flexibility, scalability, and ability to foster continuous deployment. NestJS, a progressive Node.js framework, provides excellent support for building microservices. It allows developers to create modular, maintainable applications that can grow and scale independently.
In this blog post, we will explore how to build a microservices application using NestJS with a real-world example. We’ll cover the basics of NestJS microservices, set up a simple project, and implement a service that communicates between multiple microservices using Kafka.
Understanding NestJS Microservices
NestJS provides an easy way to create microservices that can communicate with each other using different transport layers such as TCP, Redis, NATS, and Kafka. The framework allows you to define microservices with minimal boilerplate code, making it easy to focus on business logic.
Key Features:
- Modularity: Each microservice can be developed, tested, and deployed independently.
- Scalability: Services can be scaled based on demand.
- Inter-Service Communication: NestJS provides various options for communication between services, including message brokers like Kafka.
Setting Up a NestJS Microservices Project
Prerequisites
- Node.js installed on your machine
- Basic understanding of TypeScript and NestJS
- Apache Kafka installed or a cloud provider (e.g., Confluent Cloud) with access to a Kafka instance
Step 1: Creating a New NestJS Project
First, install the Nest CLI if you haven’t already:
npm i -g @nestjs/cli
Create a new project:
nest new nest-microservices
cd nest-microservices
Step 2: Installing Required Packages
We will use @nestjs/microservices and kafkajs for Kafka support. Install them using npm:
npm install @nestjs/microservices kafkajs
Step 3: Creating Microservices
For our example, let’s create two microservices: Users and Products. Each will handle its own data and will communicate via Kafka.
Creating the Users Microservice
- Generate a new module and service:
nest generate module users nest generate service users - In the
users.service.ts, implement basic user management functionality: - Create a controller to handle HTTP requests:
nest generate controller users - In
users.controller.ts, set up endpoints: - In the
main.tsfile, set up the microservice:
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
private users = [];
createUser(user: { name: string; age: number }) {
this.users.push(user);
return user;
}
findAll() {
return this.users;
}
}
import { Controller, Post, Body, Get } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() user: { name: string; age: number }) {
return this.usersService.createUser(user);
}
@Get()
findAll() {
return this.usersService.findAll();
}
}
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.KAFKA,
options: {
client: {
clientId: 'users-service',
brokers: ['localhost:9092'], // Replace with your Kafka broker address
},
consumer: {
groupId: 'users-consumer', // Unique group ID for this consumer
},
},
});
await app.listen();
}
bootstrap();
Creating the Products Microservice
Repeat similar steps for the Products microservice:
- Generate the module and service:
nest generate module products nest generate service products - Implement the
products.service.ts:import { Injectable } from '@nestjs/common'; @Injectable() export class ProductsService { private products = []; createProduct(product: { name: string; price: number }) { this.products.push(product); return product; } findAll() { return this.products; } } - Set up the controller for products:
nest generate controller products - In
products.controller.ts, set up endpoints: - Set up the microservice in
main.ts:
import { Controller, Post, Body, Get } from '@nestjs/common';
import { ProductsService } from './products.service';
@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
@Post()
create(@Body() product: { name: string; price: number }) {
return this.productsService.createProduct(product);
}
@Get()
findAll() {
return this.productsService.findAll();
}
}
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.KAFKA,
options: {
client: {
clientId: 'products-service',
brokers: ['localhost:9092'], // Replace with your Kafka broker address
},
consumer: {
groupId: 'products-consumer', // Unique group ID for this consumer
},
},
});
await app.listen();
}
bootstrap();
Inter-Service Communication with Kafka
To enable communication between the Users and Products microservices using Kafka, we will set up message patterns.
Step 1: Sending Messages from Users Service to Products Service
In the Users service, we will publish an event to Kafka whenever a user is created. Modify the UsersService to include Kafka publishing:
import { Injectable, Inject } from '@nestjs/common';
import { ClientKafka } from '@nestjs/microservices';
@Injectable()
export class UsersService {
constructor(@Inject('PRODUCTS_SERVICE') private readonly client: ClientKafka) {}
async createUser(user: { name: string; age: number }) {
const createdUser = { name: user.name, age: user.age };
// Publish user created event
await this.client.emit('user_created', createdUser);
return createdUser;
}
// Other methods...
}
Step 2: Consuming Messages in Products Service
In the Products service, we will listen for the user_created event and handle it:
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
@Controller('products')
export class ProductsController {
// Other methods...
@MessagePattern('user_created')
handleUserCreated(user: { name: string; age: number }) {
console.log(`User created: ${JSON.stringify(user)}`);
// You can add logic here to create related products or perform actions
}
}
Testing the Microservices
Now that our microservices are set up, let’s run them:
- Start the Users microservice:
cd users npm run start --watch - In a separate terminal, start the Products microservice:
cd products npm run start --watch - Use a tool like Postman or curl to test the endpoints.
To create a user:
curl -X POST http://localhost:3000/users -H 'Content-Type: application/json' -d '{"name": "John Doe", "age": 30}'
To retrieve all users:
curl -X GET http://localhost:3000/users
To create a product:
curl -X POST http://localhost:3001/products -H 'Content-Type: application/json' -d '{"name": "Product 1", "price": 100}'
To retrieve all products:
curl -X GET http://localhost:3001/products
Patterns
Microservices recognize both messages and events by patterns. A pattern is a plain value, for example, a literal object or a string. Patterns are automatically serialized and sent over the network along with the data portion of a message. In this way, message senders and consumers can coordinate which requests are consumed by which handlers.
Request-Response
The request-response message style is useful when you need to exchange messages between various external services. With this paradigm, you can be certain that the service has actually received the message (without the need to manually implement a message ACK protocol). However, the request-response paradigm is not always the best choice. For example, streaming transporters that use log-based persistence, such as Kafka or NATS streaming, are optimized for solving a different range of issues, more aligned with an event messaging paradigm.
Conclusion
In this blog post, we’ve explored the fundamentals of creating microservices using NestJS. We developed two microservices, Users and Products, that communicate via Kafka. This example demonstrates how easy it is to build scalable and maintainable applications using NestJS.
With NestJS, you can build complex microservices architectures that are easy to manage and scale. The framework’s powerful features and ease of use make it an excellent choice for modern web applications.
Feel free to experiment with this example, add more features, and explore the various transport layers NestJS offers for microservices!
