Code Journal

Microservices with NestJS

APR 1, 2023

backendmicroservicesnodeJS

A practical walkthrough for building microservices in NestJS using gRPC and RabbitMQ — from defining protobuf services to wiring up the client and message queue in your application.

Microservices with NestJS

What are microservices?

Microservices are a software architecture focused on building applications from small, independent pieces of functionality that run as separate services. Each service runs in its own process and communicates with other services through a well-defined interface. This architecture makes it easier to develop, deploy, and maintain complex applications by letting teams focus on individual services and change them without affecting others.

What is NestJS?

NestJS is a Node.js framework used to build scalable, maintainable applications. It's built on the best practices of object-oriented programming, functional programming, and reactive programming. NestJS also supports multiple communication technologies, including gRPC and RabbitMQ.

What is gRPC?

gRPC is a high-performance, low-overhead RPC (Remote Procedure Call) communication framework that uses protocol buffers to define message structure and service interfaces. gRPC supports multiple programming languages, making it ideal for distributed systems and microservice architectures.

What is RabbitMQ?

RabbitMQ is an open-source message broker used to send and receive messages between applications. It acts as an intermediary between message producers and consumers, enabling asynchronous communication between services.

Building microservices in NestJS with gRPC and RabbitMQ

To build microservices in NestJS using gRPC and RabbitMQ, follow these steps:

Step 1: Create a new NestJS project

Create a new NestJS project using the Nest CLI:

nest new project_name

This creates a new NestJS project in a folder named project_name.

Step 2: Add the gRPC and RabbitMQ packages

Install the required packages:

npm install @nestjs/microservices @grpc/grpc-js protobufjs
npm install amqplib

Step 3: Create a gRPC service

Create a gRPC service using a protocol buffers prototype. Create a .proto file in the src folder:

syntax = "proto3";

package package_name;

service ServiceName {
  rpc ServiceMethod (MessageName) returns (MessageName) {}
}

message MessageName {
  string field1 = 1;
  int32 field2 = 2;
}

Replace package_name, ServiceName, ServiceMethod, MessageName, field1, and field2 with the names and fields you want for your service. Then compile the .proto file:

npx grpc_tools_node_protoc --js_out=import_style=commonjs,binary:./src --grpc_out=./src --plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` ./src/filename.proto

This generates a .js file in src with the same name as the .proto file, containing the generated code for your gRPC service.

Step 4: Configure the gRPC server

Create a grpc.server.ts file in src:

import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';

const grpcServerOptions: MicroserviceOptions = {
  transport: Transport.GRPC,
  options: {
    package: 'package_name',
    protoPath: join(__dirname, 'filename.proto'),
  },
};

export default grpcServerOptions;

Step 5: Create a RabbitMQ service

Create a rabbitmq.service.ts file in src:

import { Injectable } from '@nestjs/common';
import { connect } from 'amqplib';

@Injectable()
export class RabbitMQService {
  private connection;
  private channel;

  async onModuleInit() {
    this.connection = await connect('amqp://localhost');
    this.channel = await this.connection.createChannel();
    await this.channel.assertQueue('queue_name');
  }

  async sendMessage(message: string) {
    await this.channel.sendToQueue('queue_name', Buffer.from(message));
  }

  async receiveMessage() {
    await this.channel.consume('queue_name', (message) => {
      console.log(message.content.toString());
    });
  }

  async onModuleDestroy() {
    await this.channel.close();
    await this.connection.close();
  }
}

Replace queue_name with your desired RabbitMQ queue name.

Step 6: Configure the gRPC client

Create a grpc.client.ts file in src:

import { Client, ClientGrpc, Transport } from '@nestjs/microservices';
import { join } from 'path';

const grpcClientOptions: ClientOptions = {
  transport: Transport.GRPC,
  options: {
    package: 'package_name',
    protoPath: join(__dirname, 'filename.proto'),
    url: 'localhost:50051',
  },
};

export interface GrpcClient {
  serviceMethod(data: MessageName): Observable<MessageName>;
}

export const grpcClientProvider = {
  provide: 'GRPC_CLIENT',
  useFactory: (client: ClientGrpc) => client.getService<GrpcClient>('ServiceName'),
  inject: ['GRPC_CLIENT'],
};

export default grpcClientOptions;

Step 7: Wire up the gRPC client and RabbitMQ service

Finally, integrate both in your app.module.ts:

import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import grpcServerOptions from './grpc.server';
import grpcClientOptions, { grpcClientProvider } from './grpc.client';
import { RabbitMQService } from './rabbitmq.service';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'GRPC_CLIENT',
        transport: Transport.GRPC,
        options: grpcClientOptions,
      },
    ]),
  ],
  controllers: [AppController],
  providers: [
    AppService,
    grpcClientProvider,
    grpcServerOptions,
    RabbitMQService,
  ],
})
export class AppModule {}

You can then use the RabbitMQ service and the gRPC client in any component or controller of your NestJS project. For example, in app.service.ts you could send a message to the RabbitMQ queue and receive a response from the gRPC server like this:

import { Injectable } from '@nestjs/common';
import { GrpcClient } from './grpc.client';
import { RabbitMQService } from './rabbitmq.service';

@Injectable()
export class AppService {
  constructor(
    private readonly rabbitmqService: RabbitMQService,
    private readonly grpcClient: GrpcClient,
  ) {}

  async sendMessage(message: string): Promise<string> {
    await this.rabbitmqService.sendMessage(message);

    const response = await this.grpcClient.serviceMethod({
      messageField: message,
    }).toPromise();

    return response.messageField;
  }
}

In summary, building microservices in NestJS with gRPC and RabbitMQ can look complex at first, but following the steps above makes it straightforward to integrate both services into your project. Doing so lets you take advantage of gRPC's efficient communication and clear service definitions, combined with RabbitMQ's scalability and resilience in the face of failures.