Create NestJS Microservices using RabbitMQ - Part 1
Tags: nestjs, typescript, microservices, rabbitmq
Published: 2022-02-06 18:05:12

Using Nestjs and RabbitMQ, we will build a microservices architecture in this tutorial.

We'll also look at how to use Docker and Docker-compose to containerize the Nestjs microservice application.

Let's begin by discussing the technology stack we're using.

We are using the Nestjs framework for the server and it is using a generally angular-like structure. It offers dependency injection, typescript support for third-party libraries, internal microservices, support for communication protocols, and many more features. for more, check out its documentation [here](https://docs.nestjs.com).

RabbitMQ will be used as a channel for communication between microservices, since it is more reliable and effective in terms of performance. On the network, it transfers data via the AMQP protocol.

Kong open-source gateway as the API gateway. Kong API gateway offers reverse proxy and using Kong api, we can route our APIs to a particular service. Kong also provides third-party plugins for ex. rate-limiting, JWT authentication.

For database we're using postgres with typeorm as NestJS have built in support for typeorm. 

For architecture, we will use the below pattern for the current microservices application. 
```text
main repo
- user (NestJS cli project)
  - Dockerfile
- token (NestJS cli project)
  - Dockerfile
.gitsubmodules - (contains configs of git submodules being used in main repo)
docker-compose.yml - will contain runtime env
```

Let's configure the docker-compose file,

```yaml
version: "3"
services:
postgres:
  image: postgres:latest
  ports:
    - "5432:5432"
  environment:
    - POSTGRES_USER=admin
    - POSTGRES_PASSWORD=master123
    - POSTGRES_DB=postgres
  volumes:
    - pg_data:/var/lib/postgresql/data
  networks:
    - backend
rabbitmq:
  image: rabbitmq:3-management
  volumes:
    - rabbit_data:/var/lib/rabbitmq
  ports:
    - "5672:5672"
    - "15672:15672"
  networks:
    - backend
networks:
backend:
  driver: bridge
volumes:
pg_data:
  driver: local
```

Now, create a fresh Nest project using Nest CLI for user service and create a microservice in the main.ts file.
To connect with microservices, we'll use RabbitMQ URL and queue name as a RabbitMQ configuration.

```typescript
app.connectMicroservice({
  transport: Transport.RMQ,
  options: {
    urls: [`${configService.get('rb_url')}`],
    queue: `${configService.get('user_queue')}`,
    queueOptions: { durable: false },
    prefetchCount: 1,
  },
});

await app.startAllMicroservices();
await app.listen(configService.get('servicePort'));
logger.log(`User service running on port ${configService.get('servicePort')}`);
```
Microservice configuration is completed for the NestJS application. now, we will move on controller file to develop routing.

```typescript
@AllowUnauthorizedRequest()
@Post('/signup')
signup(@Body() data: CreateUserDto): Promise<IAuthPayload> {
  return this.appService.signup(data);
}
```
Now, create token service in another NestJs project. and in the `main.ts` file

```typescript
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
  AppModule,
  {
    transport: Transport.RMQ,
    options: {
      urls: [`${configService.get('rb_url')}`],
      queue: `${configService.get('token_queue')}`,
      queueOptions: { durable: false },
    },
  },
);
await app.listen();
logger.log('Token service started');
```

Create `app.controller.ts` file in the token service. to use RabbitMQ as our inter-service communication, we will add message patterns to communicate between the two services. this will create an event listener in the token service.

```typescript
@MessagePattern('token_create')
public async createToken(@Payload() data: any): Promise<ITokenResponse> {
  return this.appService.createToken(data.id);
}

@MessagePattern('token_decode')
public async decodeToken(
  @Payload() data: string,
): Promise<string | JwtPayload | IDecodeResponse> {
  return this.appService.decodeToken(data);
}
```

In `app.service.ts` add generate token function.

```typescript
public createToken(userId: number): ITokenResponse {
  const accessExp = this.configService.get('accessExp');
  const refreshExp = this.configService.get('refreshExp');
  const secretKey = this.configService.get('secretKey');
  const accessToken = sign({ userId }, secretKey, { expiresIn: accessExp });
  const refreshToken = sign({ userId }, secretKey, { expiresIn: refreshExp });
  return {
    accessToken,
    refreshToken,
  };
}

public async decodeToken(
  token: string,
): Promise<string | JwtPayload | IDecodeResponse> {
  return decode(token);
}
```
Other services that are linked to the RabbitMQ instance use `token queue` and are ready to provide data to the token service.

In general, having separate queues for each microservice is a good idea to improve communication.

Add the lines below to `app.module.ts` to inform the user service that we are using the token service in order to connect the token service with the user service. 
```typescript
import: [
ClientsModule.registerAsync([
    {
      name: 'TOKEN_SERVICE',
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        transport: Transport.RMQ,
        options: {
          urls: [`${configService.get('rb_url')}`],
          queue: `${configService.get('token_queue')}`,
          queueOptions: {
            durable: false,
          },
        },
      }),
      inject: [ConfigService],
    },
])
```
In `app.service.ts` file of user service. to connect the token service.

```typescript
constructor(
  @Inject('TOKEN_SERVICE') private readonly tokenClient: ClientProxy,
) {
  this.tokenClient.connect();
}
```
Create `app.service.ts` file in user service.

```typescript
public async signup(data: CreateUserDto) {
  try {
    const { email, password, firstname, lastname } = data;
    const checkUser = await this.userRepository.findUserAccountByEmail(email);
    if (checkUser) {
      throw new HttpException('USER_EXISTS', HttpStatus.CONFLICT);
    }
    const hashPassword = this.createHash(password);
    const newUser = new User();
    newUser.email = data.email;
    newUser.password = hashPassword;
    newUser.firstName = firstname.trim();
    newUser.lastName = lastname.trim();
    newUser.role = Role.USER;
    const user = await this.userRepository.save(newUser);
    const createTokenResponse = await firstValueFrom(
      this.tokenClient.send('token_create', JSON.stringify(user)),
    );
    delete user.password;
    return {
      ...createTokenResponse,
      user,
    };
  } catch (e) {
    throw new InternalServerErrorException(e);
  }
}
```
Using RabbitMQ, which is configured in the main.ts file of the token service, the token client is a client proxy that is linked to the token service microservice instance in this instance.

We may contact the token service from the user service since the 'token create' message pattern has already been specified in the token service.

An observable in NestJS will be the message pattern response.
In order to transform the answer from the observable to a promise, use the Rxjs `firstValueFrom` method.

We will see `event pattern` example in next part of the [blog](https://dev.to/hmake98/create-nestjs-microservices-using-rabbitmq-part-2-121b).

Thanks for reading this. If you've any queries, feel free to email me at harsh.make1998@gmail.com. 
Until next time!