Handling gRPC services with Nest.js

Handling gRPC services with Nest.js

In Nest.js, we have some transport layer implementations that are prepared for microservices. One of them is the gRPC transporter which is definitely one of the most interesting. In this article, we’ll explore the idea behind this layer and how we can implement it in NestJS.

Creation of a project model

$ node -v  
v10.14.1  
$ yarn global add @nestjs/cli

Install the necessary packages

$ yarn add @nestjs/microservices @grpc/proto-loader grpc protobufjs

This time I created a project with Nestjs CLI and defined a module called module rest.

$ nest new nestjs-grpc-client  
$ nest g mo rest

With let’s change the file main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3001);
}
bootstrap();

Let’s now create the .proto file’s job is to describe the service and the methods we want to use remotely.

syntax = "proto3";

import "rpc/champion.proto";

package rpc;

service Rpc {
  rpc GetChampion (GetChampionRequest) returns (GetChampionResponse);
  rpc ListChampions (Empty) returns (ListChampionsResponse);
}

message Empty {}

message GetChampionRequest {
  int32 champion_id = 1;
}

message GetChampionResponse {
  champion.Champion champion = 1;
}

message ListChampionsResponse {
  repeated champion.Champion champions = 1;
}
///protos/rpc/champion.proto

syntax = "proto3";

package champion;

message Champion {
  enum ChampionType {
    UNKNOWN  = 0;
    FIGHTER  = 1;
    TANK     = 2;
    SUPPORT  = 3;
  }
  int32        champion_id = 1;
  ChampionType type        = 2;
  string       name        = 3;
  string       message     = 4;
}

A controller that provides RPC server functionality as a REST API

Antes de escrever nosso controller vamos configurar as opções que serão passada para o decorator @Client que será importado da biblioteca @nestjs/microservices.

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

const protoDir = join(__dirname, '..', 'protos');
export const grpcClientOptions: ClientOptions = {
  transport: Transport.GRPC,
  options: {
    url: '0.0.0.0:5000',
    package: 'rpc',
    protoPath: '/rpc/rpc.proto',
    loader: {
      keepCase: true,
      longs: Number,
      enums: String,
      defaults: false,
      arrays: true,
      objects: true,
      includeDirs: [protoDir],
    },
  },
};

In this example, we assume a use case where another gRPC server is called a web server built with Nest.js, and its function is provided as a REST API.

Next is the part that calls the function of the service that manages the Controller’s gRPC. The gRPC client is acquired by Decorator @Client the service actually used is acquired dynamically at the time of the lifecycle hook onModuleInit.

When I tried to get the service in Builder without using this lifecycle hook, an error occurred, so it seems better to use it silently.

import { Controller, Get, Query, OnModuleInit } from '@nestjs/common';
import { Client, ClientGrpc } from '@nestjs/microservices';
import {
  RpcService,
  Empty,
  GetChampionResponse,
  GetChampionRequest,
  ListChampionsResponse,
} from '../../types';
import { grpcClientOptions } from '../grpc-client.options';

@Controller('rest')
export class RestController implements OnModuleInit {
  @Client(grpcClientOptions) private readonly client: ClientGrpc;
  private rpcService: RpcService

  onModuleInit(): void {
    this.rpcService = this.client.getService<RpcService>('Rpc');
  }

  @Get('champion')
  async getChampion(
    @Query() request: GetChampionRequest,
  ): Promise<GetChampionResponse> {
    return this.rpcService.getChampion(request);
  }

  @Get('champions')
  async getChampions(@Query() request: Empty): Promise<ListChampionsResponse> {
    return this.rpcService.listChampions(request);
  }
}

In the real use situation, a pattern that responds after processing the information acquired from the gRPC server on the REST server-side is assumed.

In this case, I think it will be done in the service layer instead of dealing with the gRPC client directly in the controller like this time.

You can see that gRPC server content can be called a REST API as follows.

...{
"champion": {
"champion_id": 1,
"type": 1,
"name": "Felipe Marques",
"message": "uhuuuuu you are champion"
}
}...

Conclusion

The Nest.js microservices package itself still looks underdeveloped (the interfaces are defined but not implemented), I welcome gRPC microservice development with Nest.js so I would like to continue using it in the future.

Comments