gRPC with NestJS: A Comprehensive Beginner's Guide
Published: 2024-06-08 17:34:50

The world of software development is changing quickly, and creating scalable and effective microservices is crucial. It is frequently difficult to communicate across different services, particularly when low latency and great performance are desired. Introducing gRPC, a potent, open-source RPC framework developed by Google. When paired with NestJS, an advanced Node.js framework, gRPC offers a reliable inter-service communication solution. You will learn the basics of gRPC, how to integrate it with NestJS, and develop a basic chat service as an example by following this guide.

## Understanding the Fundamentals
**What is gRPC?**
Google created the open-source gRPC (Google Remote Procedure Call) RPC (Remote Procedure Call) framework. It uses Protocol Buffers (protobuf) for serialization and HTTP/2 for transport to facilitate effective, language-neutral communication between distributed systems.

Key Features of gRPC:

- Language Independence: Supports multiple programming languages.

- HTTP/2 Support: Provides features like multiplexing and header compression.

- Bidirectional Streaming: Supports streaming of data between client and server.

- Automatic Code Generation: Reduces boilerplate code through protobuf definitions.

## What is NestJS?

NestJS is a Node.js framework for creating scalable, dependable, and effective server-side applications. It makes use of both TypeScript and contemporary JavaScript features to offer a powerful development environment.

**Key Features of NestJS:**

- Modular Architecture: Encourages a modular approach to application design.

- Dependency Injection: Simplifies management of dependencies.

- Extensive CLI: Provides tools to generate boilerplate code and manage the project.

- Support for Multiple Transport Layers: Includes HTTP, WebSocket, and gRPC.

## Setting Up the Project

**Step 1: Creating a New NestJS Project**
First, let's use the Nest CLI to establish a new NestJS project:
```
nest new grpc-chat-service
```
Navigate into the project directory:
```
cd grpc-chat-service
```
**Step 2: Installing Necessary Dependencies**
To integrate gRPC with NestJS, install the necessary dependencies:
```
npm install @nestjs/microservices grpc @grpc/proto-loader
```

## Defining the Protocol Buffers File

Protocol Buffers (protobuf) is an extendable, platform- and language-neutral method for serializing structured data. To specify our service contract, create a file called chat.proto in the src directory:
```
syntax = "proto3";

service ChatService {
  rpc SendMessage (Message) returns (Empty);
}

message Message {
  string content = 1;
}

message Empty {}

```
In this definition:

- Service: ChatService contains an RPC method SendMessage.

- Messages: Defines the structure of Message and Empty.

## Implementing the Server
Let's now put the server-side logic for our chat service into practice.
**Step 1: Create a Controller**
```
Create a new file named chat.controller.ts in the src directory:
import { Controller } from '@nestjs/common';
import { GrpcMethod } from '@nestjs/microservices';
import { Empty, Message } from './chat.grpc.pb';

@Controller()
export class ChatController {
  private messages: string[] = [];

  @GrpcMethod('ChatService', 'SendMessage')
  async sendMessage(data: Message): Promise<Empty> {
    console.log('Received message:', data.content);
    this.messages.push(data.content);
    console.log('Current messages:', this.messages);
    return {};
  }
}
```
- @GrpcMethod: Decorator to map the gRPC method to the NestJS method.
- sendMessage Method: Logs the received message, stores it in an in-memory array, and prints the current list of messages.
**Step 2: Update the Module**
Update app.module.ts to include the ChatController:
```
import { Module } from '@nestjs/common';
import { ChatController } from './chat.controller';

@Module({
  controllers: [ChatController],
})
export class AppModule {}
```

## Implementing the Client

We'll now build a client in order to communicate with our gRPC server.
**Step 1: Create a Client Class**
Create a new file named chat.client.ts in the src directory:
```
import { Injectable } from '@nestjs/common';
import { ClientGrpc, ClientProxyFactory, Transport } from '@nestjs/microservices';
import { Message } from './chat.grpc.pb';
import { join } from 'path';

@Injectable()
export class ChatClient {
  private readonly client: ClientGrpc;

  constructor() {
    this.client = ClientProxyFactory.create({
      transport: Transport.GRPC,
      options: {
        url: 'localhost:5000',
        package: 'chat',
        protoPath: join(__dirname, 'chat.proto'),
      },
    });
  }

  async sendMessage(content: string): Promise<void> {
    const message: Message = { content };
    await this.client.getService('ChatService').sendMessage(message).toPromise();
  }
}
```
In this client:

- ClientProxyFactory.create: Creates a gRPC client.

- sendMessage Method: Sends a message to the server.

**Step 2: Integrate Client in Module**
Update app.module.ts to include the ChatClient:

```
import { Module } from '@nestjs/common';
import { ChatClient } from './chat.client';
import { ChatController } from './chat.controller';

@Module({
  controllers: [ChatController],
  providers: [ChatClient],
})
export class AppModule {}

```

## Bootstrapping the Application

Bootstrapping is the process of starting the application, initializing necessary services, and getting the server up and running.
Update the main.ts file:

```
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Transport } from '@nestjs/microservices';
import { join } from 'path';

async function bootstrap() {
  const app = await NestFactory.createMicroservice(AppModule, {
    transport: Transport.GRPC,
    options: {
      url: 'localhost:5000',
      package: 'chat',
      protoPath: join(__dirname, 'chat.proto'),
    },
  });
  await app.listenAsync();
}
bootstrap();
```
In this code:

- NestFactory.createMicroservice: Creates a microservice instance.

- Transport Options: Specifies gRPC as the transport and provides necessary configuration like URL, package, and path to the protobuf file

- app.listenAsync: Starts the microservice and listens for incoming gRPC requests.

## Running and Testing the Application

**Step 1: Start the Server**
Run the server:
```
npm run start:dev
```
**Step 2: Use the Client to Send a Message**
We can now use the ChatClient to send a message to our server. You can create a simple script to test this:
Create test-client.ts:

```
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ChatClient } from './chat.client';

async function testClient() {
  const app = await NestFactory.createApplicationContext(AppModule);
  const client = app.get(ChatClient);
  await client.sendMessage('Hello, gRPC!');
  await app.close();
}

testClient();
```
Run the script:

```
`ts-node src/test-client.ts`
```
You should see the server logging the received message and maintaining a list of messages.

## Conclusion

We have examined how to integrate gRPC with NestJS to create effective microservices in this extensive article. We've gone over the basics of gRPC, used Protocol Buffers to build a service, used an in-memory message storage to provide server-side functionality, built a message-sending client, and bootstrapped our NestJS application. You now have the basis to use gRPC's power in your NestJS apps, allowing for high-performance and scalable inter-service communication. Just follow these instructions. Using gRPC and NestJS together provides a reliable and effective solution whether you're developing microservices, real-time apps, or intricate distributed systems.