In today’s rapidly evolving tech landscape, microservices architecture has emerged as a popular approach to designing scalable and resilient applications. This architecture allows you to break down a monolithic application into smaller, independently deployable services that work cohesively. One powerful framework that facilitates this approach in the Node.js ecosystem is NestJS.
In this article, we’ll delve into how to build microservices with NestJS, exploring essential concepts, features, and providing practical examples.
Why Choose NestJS for Microservices?
NestJS is a progressive Node.js framework built with TypeScript, designed to offer a mature and versatile development experience. Here are some reasons why NestJS stands out for microservices architecture:
- Modular Design: NestJS promotes a highly modular structure, making it easy to organize code and manage dependencies.
- Built-in Support: NestJS offers seamless support for microservices with pre-configured transport layers like TCP, gRPC, Redis, and NATS.
- TypeScript: Being built with TypeScript ensures type safety and robust code.
Setting Up Your Project
First, let’s start by setting up a new NestJS project. To scaffold a NestJS application, you need the Nest CLI installed:
npm i -g @nestjs/cli
nest new microservice-demo
Navigate to the newly created directory:
cd microservice-demo
Now you have a project set up with the basic scaffold provided by the Nest CLI.
Creating a Basic Microservice
Let’s create a basic microservice for user management. In a real-world scenario, this might involve operations such as user registration, authentication, and retrieval of user details.
- Generate a new module and service for users:
nest g module users nest g service users
- Inside the
users.service.ts
file, implement some basic logic:
import { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { private readonly users = new Map<string, any>(); createUser(id: string, name: string) { this.users.set(id, { id, name }); } getUser(id: string) { return this.users.get(id); } }
- Now, let’s create a controller to handle incoming requests. Generate a controller and use the service in it:
nest g controller users
- Inside
users.controller.ts
, wire up the routes:
import { Controller, Get, Param, Post, Body } from '@nestjs/common'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Post() createUser(@Body() body: { id: string, name: string }) { this.usersService.createUser(body.id, body.name); } @Get(':id') getUser(@Param('id') id: string) { return this.usersService.getUser(id); } }
Microservice Communication
NestJS simplifies microservice communication using transporters. We’ll demonstrate communication using TCP transport.
Setting Up the Gateway Service
First, let’s create a gateway to handle communication between microservices.
- Generate a new NestJS application for the gateway:
nest new gateway
- Open
main.ts
of the gateway application and set up the microservice client:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { Transport } from '@nestjs/microservices'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.connectMicroservice({ transport: Transport.TCP, options: { host: '127.0.0.1', port: 3001 }, }); await app.startAllMicroservicesAsync(); await app.listen(3000); } bootstrap();
Setting Up the User Microservice
- Open
main.ts
of the user microservice and configure it as a microservice application:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { Transport, MicroserviceOptions } from '@nestjs/microservices'; async function bootstrap() { const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, { transport: Transport.TCP, options: { host: '127.0.0.1', port: 3001 }, }); await app.listenAsync(); } bootstrap();
Implementing Microservice Messaging
Modify the user service to handle messages from the gateway.
- In
users.service.ts
, enable message patterns:
import { Injectable } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; @Injectable() export class UsersService { private readonly users = new Map<string, any>(); @MessagePattern({ cmd: 'create_user' }) createUser(data: { id: string, name: string }) { this.users.set(data.id, { id: data.id, name: data.name }); return { status: 'success' }; } @MessagePattern({ cmd: 'get_user' }) getUser(id: string) { return this.users.get(id); } }
- In the gateway, send messages to the user microservice:
import { Controller, Get, Param, Post, Body } from '@nestjs/common'; import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices'; @Controller('users') export class UsersController { private client: ClientProxy; constructor() { this.client = ClientProxyFactory.create({ transport: Transport.TCP, options: { host: '127.0.0.1', port: 3001 }, }); } @Post() async createUser(@Body() body: { id: string, name: string }) { return this.client.send({ cmd: 'create_user' }, body).toPromise(); } @Get(':id') async getUser(@Param('id') id: string) { return this.client.send({ cmd: 'get_user' }, id).toPromise(); } }
Conclusion
With NestJS, setting up a microservice architecture becomes straightforward and efficient. Its modular design and built-in support for microservices enable developers to build scalable and maintainable applications seamlessly. By following this guide, you can start building your own microservices-based systems with NestJS.
Feel free to explore more advanced topics like error handling, service discovery, and more transporters as you become comfortable with the basics. Happy coding!