Building Scalable Microservices with NestJS
One of the many features that nestJs offers is its native support of microservices architecture. When using microservices, you can use all the features that nests have in a normal monolithic architecture, like decorators,guards, pipes, etc. But before we jump into code implementation, let's go back and talk about what microservices are and some core concepts you need to understand. What Are microServices: when dealing with an application programming interface (API) we usually send a request to one server that handles all kinds of requests that our application might need, it handles all of the logic inside on app or server and sends the appropriate response On the other hand Micro Services pattern is quite different instead of having one app or server handling all of the requests we have multiple servers called service, each one of them is responsible for handling some part of our app logic, but you may ask why would we want to use this instead of our traditional architecture and avoid all of the overhead of setting it up,w ell why micro Services: Separation of concerns and decoupling:w ith this setup, every service handles some aspect of our application so it is easier to scale and locate errors if they exist fault Taulernce: because each service exists as an independent node if it goes down it only affects the logic aspect in handles, not the whole server scalability and performance: when we separate services into different nodes each one of them will have less job to do because it only focuses on some aspect of the app, not the whole app, with each node existing as a standalone app we can easily scale the services that are used frequently Now after that, you might Ask : Now, you might ask: how do these services communicate with each other? Ande we need to introduce the concept of a transport layer, you can imagine it for now as the route or the bridge that links all of the services together it sends data across multiple services :fmo us transport layers are : TCP RMQ BullMq Grpc and a lot more. What is cool about nests that is allows you to use your preferred transport layer from the multiple ones it offers and you can easily change it in the configuration without changing the code later (say you are using Tcp and after a while, you wanted to switch over to a message queue like RMQ you just have to change it in the configuration of the module without changing anything in the code) Before we jump into coding (I know it took too long but this is not your average code tutorial blog we need to understand what exactly each piece of code does when we write it) we need to discuss one last thing that is the types of microservices, in this context ,we are focusing on types regarding how are req-res are handled and we will focus on 2 types: Request-response (Message pattern): Here we send a message( which is just a piece of data) over to the right service over a transport layer, and then wait for it to return a response to use Event-based Here we are also sending a message or in this case, it is called an event but we don't actually expect or wait for a response instead side effects are just happening in the appropriate service(s) In most cases events are unidirectional meaning that the data only goes in one way now that we have a basic understanding of what micro services architecture is all about let's dive into how to implement it using nestJs Example of A micro Services System here is what a micro Services system look like NestJs Implementation: We will try to build a simple e-commerce example to showcase microServices in nests First we Create a normal nests application this application is often called the producer because unlike other nodes it is an HTTP app that is responsible for getting the requests and sending them to the appropriate service then we need to install the microServices module $ npm i --save @nestjs/microservices 3 . Install the transport layer here I'm using Rabbit mq feel free to use anyone you like (check the documentation for supported Transport layers) $ npm i --save amqplib amqp-connection-manager We define a simple dto for validation import { IsEmail, IsNumber, IsString } from 'class-validator'; export class orderDto { @IsString() product: string; @IsEmail() email: string; @IsNumber() quantity: number; } Then we register our services inside the app Module or the feature module if you want better separation, using the microservices module that we just downloaded here we will only use the payment and orders service import { OrdersController } from './orders.controller'; import { ClientsModule, Transport } from '@nestjs/microservices'; @Module({ imports: [ ClientsModule.register([ { name: 'ORDER_SERVICE', transport: Transport.RMQ, options: { URLs: ['amqp://localhost:5672'], queue

One of the many features that nestJs offers is its native support of microservices architecture. When using microservices, you can use all the features that nests have in a normal monolithic architecture, like decorators,guards, pipes, etc. But before we jump into code implementation, let's go back and talk about what microservices are and some core concepts you need to understand.
What Are microServices:
when dealing with an application programming interface (API) we usually send a request to one server that handles all kinds of requests that our application might need, it handles all of the logic inside on app or server and sends the appropriate response On the other hand Micro Services pattern is quite different instead of having one app or server handling all of the requests we have multiple servers called service, each one of them is responsible for handling some part of our app logic, but you may ask why would we want to use this instead of our traditional architecture and avoid all of the overhead of setting it up,w ell
why micro Services:
- Separation of concerns and decoupling:w ith this setup, every service handles some aspect of our application so it is easier to scale and locate errors if they exist
- fault Taulernce: because each service exists as an independent node if it goes down it only affects the logic aspect in handles, not the whole server
- scalability and performance: when we separate services into different nodes each one of them will have less job to do because it only focuses on some aspect of the app, not the whole app, with each node existing as a standalone app we can easily scale the services that are used frequently
Now after that, you might Ask : Now, you might ask: how do these services communicate with each other?
Ande we need to introduce the concept of a transport layer, you can imagine it for now as the route or the bridge that links all of the services together it sends data across multiple services :fmo us transport layers are :
- TCP
- RMQ
- BullMq
- Grpc and a lot more.
What is cool about nests that is allows you to use your preferred transport layer from the multiple ones it offers and you can easily change it in the configuration without changing the code later (say you are using Tcp and after a while, you wanted to switch over to a message queue like RMQ you just have to change it in the configuration of the module without changing anything in the code)
Before we jump into coding (I know it took too long but this is not your average code tutorial blog we need to understand what exactly each piece of code does when we write it) we need to discuss one last thing that is the types of microservices, in this context ,we are focusing on types regarding how are req-res are handled and we will focus on 2 types:
Request-response (Message pattern):
Here we send a message( which is just a piece of data) over to the right service over a transport layer, and then wait for it to return a response to use
Event-based
Here we are also sending a message or in this case, it is called an event but we don't actually expect or wait for a response instead side effects are just happening in the appropriate service(s) In most cases events are unidirectional meaning that the data only goes in one way
now that we have a basic understanding of what micro services architecture is all about let's dive into how to implement it using nestJs
Example of A micro Services System
here is what a micro Services system look like
NestJs Implementation:
We will try to build a simple e-commerce example to showcase microServices in nests
First we Create a normal nests application this application is often called the producer because unlike other nodes it is an HTTP app that is responsible for getting the requests and sending them to the appropriate service
then we need to install the microServices module
$ npm i --save @nestjs/microservices
3 . Install the transport layer here I'm using Rabbit mq feel free to use anyone you like (check the documentation for supported Transport layers)
$ npm i --save amqplib amqp-connection-manager
We define a simple dto for validation
import { IsEmail, IsNumber, IsString } from 'class-validator';
export class orderDto {
@IsString()
product: string;
@IsEmail()
email: string;
@IsNumber()
quantity: number;
}
Then we register our services inside the app Module or the feature module if you want better separation, using the microservices module that we just downloaded here we will only use the payment and orders service
import { OrdersController } from './orders.controller';
import { ClientsModule, Transport } from '@nestjs/microservices';
@Module({
imports: [
ClientsModule.register([
{
name: 'ORDER_SERVICE',
transport: Transport.RMQ,
options: {
URLs: ['amqp://localhost:5672'],
queue: 'order_queue',
queueOptions: {
durable: true, // Make the queue persistent
},
// Specify an exchange
},
},
]),
ClientsModule.register([
{
name: 'PAYMENT_SERVICE',
transport: Transport.RMQ,
options: {
URLs: ['amqp://localhost:5672'],
queue: 'payment_queue',
queueOptions: {
durable: true, // Make the queue persistent
},
// Specify an exchange
},
},
]),
],
controllers: [OrdersController],
providers: [OrdersService],
})
export class OrdersModule {}
Here I'm using RabbitMq which is a powerful message queue
a message queue: is a transport layer that uses the FIFO Data structure and holds the messages in a queue until the first message gets processed by the consumer (service), what is cool about it is that if the consumer is down it will wait until it is up again (unlike tcp for example which if the service is down the message will just be lost)
Here is a simple controller that handles 2 requests
/* eslint-disable prettier/prettier */
import { Body, Controller, Post } from '@nestjs/common';
import { OrdersService } from './orders.service';
import { orderDto } from './orderDto';
@Controller('orders')
export class OrdersController {
constructor(private read-only ordersService: OrdersService) {
}
@Post()
OrderAdded(@Body() order:orderDto){
return this.ordersService.handleOrder(order);
}
@Post('/pay')
payOrder(@Body() order:orderDto){
return this.ordersService.handle payment(order);
}
}
And this is the service
/* eslint-disable prettier/prettier */
import { Inject, Injectable } from '@nestjs/common';
import { orderDto } from './orderDto';
import { ClientProxy } from '@nestjs/microservices';
@Injectable()
export class OrdersService {
constructor(@Inject("ORDER_SERVICE") private RSERVICE:ClientProxy,@Inject('PAYMENT_SERVICE') private PAYMENTQueue:ClientProxy ){}
handleOrder(order :orderDto){
this.RSERVICE.emit('ORDER_PLACED',order);
return {message:"ORDER ADDED!"}
}
handle payment(order: orderDto) {
this.PAYMENTQueue.emit('PAYMENT',order);
return {
message: 'PAYMENT DONE!',
}
}
}
Here is where the magic happens first we Inject services registered earlier in the module and then we emitting events the first String in the emit method is the event pattern which is how the service now what logic to execute and the second param is the payload which is the actual data that the service needs to work with.
here we are using .emit because we are using event-based architecture
if you want to use a message you just use .send instead
And then we need to create another nest application which is the service itself here is how to define the main of the app
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Transport } from '@nestjs/microservices';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.RMQ,
options: {
URLs: ['amqp://localhost:5672'],
queue: 'order_queue',
queueOptions: {
durable: true,
},
},
});
await app.listen();
}
bootstrap();
what changes that we create a microService instead of a nest application and then we define the appropriate transport layer that this service uses to communicate with the producer
and here is to define the controller to handle upcoming events
import { Controller } from '@nestjs/common';
import { OrdersService } from './orders.service';
import { Ctx, EventPattern, Payload, RmqContext } from '@nestjs/microservices';
import { OrderDto } from './orderDto';
@Controller('orders')
export class OrdersController {
constructor(private readonly ordersService: OrdersService) {}
@EventPattern('ORDER_PLACED')
handleOrders(@Payload() order: OrderDto, @Ctx() context: RmqContext) {
return this.ordersService.handleOrder(order);
}
}
note that the Event Pattern is the same one we sent Earlier
here we get Acces to the payload that we send and the Excution context which contains some metadata that are out of the scope of this article
what cool now is that we can register another Service inside this service and send events to it as well like we're doing here in the service
import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { OrderDto } from './orderDto';
@Injectable()
export class OrdersService {
constructor(
@Inject('PAYMENT_SERVICE') private readonly paymentClient: ClientProxy,
) {}
handleOrder(order: OrderDto) {
console.log('PROCESSING ORDER', order);
console.log('SENDING ORDER TO PAYMENT SERVICE');
this.paymentClient.emit('PAYMENT', order);
}
}
and Payment Client is another service built just like this one
In this article, we explored how to build a microservices architecture using NestJS and RabbitMQ. We discussed the benefits of microservices, such as scalability, fault tolerance, and separation of concerns, and demonstrated how NestJS simplifies inter-service communication.
By leveraging RabbitMQ as a message broker, we ensured reliable, asynchronous communication between services, making our system more resilient and efficient. The flexibility of NestJS also allows us to switch transport layers easily, giving us the freedom to adapt to different use cases.
As you continue building microservices, consider factors like service discovery, monitoring, and security to enhance your system further. Experiment with different transport layers, and explore tools like Kubernetes for orchestration. Microservices can be complex, but with the right tools and architecture, they can unlock powerful scalability and maintainability for your applications.