Skip to content

Behind the Codes

Creating gRPC Microservices with NestJS

NestJS, Microservices, gRPC2 min read

gRPC

In this blog post, I'm going to create a gRPC microservice using NestJS.

What is gRPC?

gRPC is an open source Remote Procedure Call developed by Google in 2015. It uses HTTP/2 for transport and Protocol Buffers to encode the data, which have a stricter specification compared with JSON. It is language-neutral and platform-neutral so it can run in any environment.

Pre-requisites

Before you begin, make sure that you have NestJS installed on your machine.

Creating the Services

We will start by creating 2 different services.

Hello World Service

The first service that we're going to create is Hello World Service. This is just going to be a simple service that returns Hello World! upon called.

Create a new NestJS project called hello-world-service.

$ nest new hello-world-service

Install the required packages.

$ npm i @nestjs/microservices grpc @grpc/proto-loader

Create a .proto file to define your protobuf.

src/protos/hello-world.proto
1syntax = "proto3";
2
3package hello;
4
5service HelloWorldService {
6 rpc GetHelloWorld (HelloWorldReq) returns (HelloWorld) {}
7}
8
9message HelloWorldReq {}
10
11message HelloWorld {
12 string reply = 1;
13}

Update your main.ts file to this:

main.ts
1import { Logger } from '@nestjs/common';
2import { NestFactory } from '@nestjs/core';
3import { MicroserviceOptions, Transport } from '@nestjs/microservices';
4import { join } from 'path';
5import { AppModule } from './app.module';
6
7const logger = new Logger();
8
9async function bootstrap() {
10 const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
11 transport: Transport.GRPC,
12 options: {
13 package: 'hello',
14 protoPath: join(__dirname, './protos/hello-world.proto'),
15 },
16 });
17 await app.listen(() => logger.log('Hello World service is listening...'));
18}
19bootstrap();

Update your app.controller.ts to this:

app.controller.ts
1import { Controller } from '@nestjs/common';
2import { GrpcMethod } from '@nestjs/microservices';
3
4@Controller()
5export class AppController {
6 @GrpcMethod('HelloWorldService', 'GetHelloWorld')
7 getHelloWorld() {
8 return { reply: 'Hello World!'};
9 }
10}

Note that we're using @GrpcMethod() decorator in our controller to tell NestJS that this method is a gRPC service method. The first argument in the decorator denotes our service name which we defined in our hello-world.proto file while the second argument denotes our RPC method within HelloWorldService in the hello-world.proto file.

Custom Hello Service

The second service that we're going to create is Custom Hello Service. This is just going to be a simple service that accept a string as params and returns Hello <params>! upon called. We will repeat several steps from the Hello World Service above.

Create a new NestJS project called custom-hello-service.

$ nest new custom-hello-service

Install the required packages.

$ npm i @nestjs/microservices grpc @grpc/proto-loader

Create a .proto file to define your protobuf.

src/protos/custom-hello.proto
1syntax = "proto3";
2
3package customHello;
4
5service CustomHelloService {
6 rpc GetCustomHello (CustomHelloReq) returns (CustomHello) {}
7}
8
9message CustomHelloReq {
10 string data = 1;
11}
12
13message CustomHello {
14 string reply = 1;
15}

Update your main.ts file to this:

main.ts
1import { Logger } from '@nestjs/common';
2import { NestFactory } from '@nestjs/core';
3import { MicroserviceOptions, Transport } from '@nestjs/microservices';
4import { join } from 'path';
5import { AppModule } from './app.module';
6
7const logger = new Logger();
8
9async function bootstrap() {
10 const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
11 transport: Transport.GRPC,
12 options: {
13 package: 'customHello',
14 protoPath: join(__dirname, './protos/custom-hello.proto'),
15 },
16 });
17 await app.listen(() => logger.log('Custom Hello service is listening...'));
18}
19bootstrap();

Update your app.controller.ts to this:

app.controller.ts
1import { Controller } from '@nestjs/common';
2import { GrpcMethod } from '@nestjs/microservices';
3
4@Controller()
5export class AppController {
6 @GrpcMethod('CustomHelloService', 'GetCustomHello')
7 getCustomHello(data: string) {
8 return { reply: `Hello ${data}!` };
9 }
10}

Same as the previous Hello World Service, the first parameter in @GrpcMethod() decorator denotes our service name which we defined in our custom-hello.proto file while the second argument denotes our RPC method within CustomHelloService in the custom-hello.proto file.

API Gateway

API gateway is a server that act as the single entry point to our system. It encapsulates internal system architecture and provides an API that is tailored to each client. Basically, API gateway is the one that is going to expose our microservices to the outside world.

Create a new NestJS project called api-gateway.

$ nest new api-gateway

Install the required packages.

$ npm i @nestjs/microservices grpc @grpc/proto-loader

Copy the .proto files from the previous services into src/protos folder.

Update your app.controller.ts file to this:

app.controller.ts
1import { Controller, Get, Param, Query } from '@nestjs/common';
2import { AppService } from './app.service';
3
4@Controller()
5export class AppController {
6 constructor(private readonly appService: AppService) {}
7
8 @Get('hello-world')
9 getHelloWorld() {
10 return this.appService.getHelloWorld();
11 }
12
13 @Get('custom-hello')
14 getCustomHello(@Query('q') q: string) {
15 return this.appService.getCustomHello(q);
16 }
17}

Update your app.service.ts file to this:

app.service.ts
1import { Injectable, OnModuleInit, Inject } from '@nestjs/common';
2import { ClientGrpc } from '@nestjs/microservices';
3import { Observable } from 'rxjs';
4
5interface HelloWorldService {
6 getHelloWorld(data: any): Observable<any>;
7}
8
9interface CustomHelloService {
10 getCustomHello(data: any): Observable<any>;
11}
12
13@Injectable()
14export class AppService implements OnModuleInit {
15 private helloWorldService: HelloWorldService;
16 private customHelloService: CustomHelloService;
17
18 constructor(
19 @Inject('HELLO_WORLD_SERVICE') private helloWorldClient: ClientGrpc,
20 @Inject('CUSTOM_HELLO_SERVICE') private customHelloClient: ClientGrpc
21 ) {}
22
23 onModuleInit() {
24 this.helloWorldService = this.helloWorldClient.getService<HelloWorldService>('HelloWorldService');
25 this.customHelloService = this.customHelloClient.getService<CustomHelloService>('CustomHelloService');
26 }
27
28 getHelloWorld() {
29 return this.helloWorldService.getHelloWorld({});
30 }
31
32 getCustomHello(data: string) {
33 return this.customHelloService.getCustomHello({ data });
34 }
35}

Lastly, update your app.module.ts file to this:

app.module.ts
1import { Module } from '@nestjs/common';
2import { ClientsModule, Transport } from '@nestjs/microservices';
3import { join } from 'path';
4import { AppController } from './app.controller';
5import { AppService } from './app.service';
6
7@Module({
8 imports: [ClientsModule.register([
9 {
10 name: 'HELLO_WORLD_SERVICE',
11 transport: Transport.GRPC,
12 options: {
13 package: 'hello',
14 protoPath: join(__dirname, './protos/hello-world.proto'),
15 },
16 },
17 {
18 name: 'CUSTOM_HELLO_SERVICE',
19 transport: Transport.GRPC,
20 options: {
21 package: 'customHello',
22 protoPath: join(__dirname, './protos/custom-hello.proto'),
23 },
24 },
25 ]),],
26 controllers: [AppController],
27 providers: [AppService],
28})
29export class AppModule {}

Testing the Services

To test the microservices, you can use Postman or cURL and do a GET method to localhost:3000/hello-world to test the Hello World Service and localhost:3000/custom-hello?q={query} to test the Custom Hello Service.

You can see the full source code here.

© 2021 by Behind the Codes. All rights reserved.
Theme by LekoArts