<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Iva-rgb</title>
    <description>The latest articles on DEV Community by Iva-rgb (@ivargb).</description>
    <link>https://dev.to/ivargb</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F589004%2F9554fb8d-692c-4b33-a56b-12d4e261b259.png</url>
      <title>DEV Community: Iva-rgb</title>
      <link>https://dev.to/ivargb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ivargb"/>
    <language>en</language>
    <item>
      <title>Hybrid NestJs Microservice Responding to Both HTTP and gRPC Requests</title>
      <dc:creator>Iva-rgb</dc:creator>
      <pubDate>Sun, 08 Sep 2024 08:19:10 +0000</pubDate>
      <link>https://dev.to/ivargb/hybrid-nestjs-microservice-responding-to-both-http-and-grpc-requests-3pll</link>
      <guid>https://dev.to/ivargb/hybrid-nestjs-microservice-responding-to-both-http-and-grpc-requests-3pll</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jocrb3fgpfbuj8eyha5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jocrb3fgpfbuj8eyha5.png" alt="Image description" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post is not intended to compare the pros and cons of gRPC versus REST. Instead, the focus is on how to combine both. &lt;/p&gt;

&lt;p&gt;In some cases, you may need your microservice to communicate with a browser via REST, while also allowing internal microservices to communicate with it. For internal communication, gRPC is often the better choice due to its speed and language-agnostic capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  File Structure and Overview
&lt;/h2&gt;

&lt;p&gt;To handle both REST and gRPC, we’ll need two controllers—one for each protocol—both communicating with a shared service. The REST setup is straightforward, but gRPC requires a few additional files in the &lt;code&gt;libs&lt;/code&gt; folder, which stores shared resources across the monorepo. The libs folder is located at the root of the project, while the microservice itself is placed in the apps folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the REST Controller
&lt;/h2&gt;

&lt;p&gt;To generate the microservice, use the following Nx command: &lt;code&gt;nx g @nx/nest:application hybrid-app&lt;/code&gt;. Afterward, rename the generated controller to &lt;em&gt;http-hybrid-app.controller.ts&lt;/em&gt;. Below is an example of the file’s contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
import { Body, Controller, Post, UseFilters } from '@nestjs/common';
import { HybridAppService } from './hybrid-app.service';
import { CustomExceptionFilter } from '@monorepo/utils';

@Controller()
@UseFilters(CustomExceptionFilter)
export class HttpHybridAppController {
  constructor(private readonly hybridAppService: HybridAppService) {}

  @Post('greet')
  public async greet(@Body() dto: { **some type** }) {
    return this.hybridAppService.greetTheUser({ ...dto });
  }

  @Post('meet')
  public async meet(@Body() dto: { **some type** }) {
    return this.hybridAppService.meetTheUser({ ...dto });
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This controller handles REST requests and communicates with a shared service to process the logic. The &lt;code&gt;@UseFilters&lt;/code&gt; decorator applies a custom exception filter to ensure consistent error handling. This setup is intentional, as it later allows us to demonstrate how error handling differs when using gRPC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the gRPC Controller
&lt;/h2&gt;

&lt;p&gt;Before setting up your gRPC controller, you first need to create a &lt;a href="https://protobuf.dev/overview/" rel="noopener noreferrer"&gt;.proto file&lt;/a&gt;, which defines the structure of the gRPC service.&lt;/p&gt;

&lt;p&gt;I’ve placed my &lt;code&gt;.proto&lt;/code&gt; files in the &lt;code&gt;libs/proto&lt;/code&gt; folder. This organization keeps the files accessible as shared resources across the monorepo. If you decide to extend this example by creating a client gRPC microservice to communicate with the hybrid service, both services will need to use the same &lt;code&gt;.proto&lt;/code&gt; file definition, making it convenient to store it in a shared location.&lt;/p&gt;

&lt;p&gt;It is expected that you have at least basic knowledge in &lt;a href="https://protobuf.dev/overview/" rel="noopener noreferrer"&gt;protobuf&lt;/a&gt; before diving in the next step, which is the content of the &lt;code&gt;hybrid.proto&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;syntax = "proto3";

package hybrid;

message GreetDto {
  string greeting = 1;
  string full_name = 2;
}

message GreetResponse {
  string greet = 1;
}

message MeetDto {
  string name = 1;
  string surname = 2;
  int32 age = 3;
}

message MeetResponse {
  string meet = 1;
}

service HybridAppService {
  rpc Greet (GreetDto) returns (GreetResponse);
  rpc Meet (MeetDto) returns (MeetResponse);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this format may seem unfamiliar, it can be converted into readable TypeScript code for use in your microservice. To do this, you need to install the &lt;a href="https://google.github.io/proto-lens/installing-protoc.html" rel="noopener noreferrer"&gt;Google protobuf compiler&lt;/a&gt;. This tool provides the &lt;code&gt;protoc&lt;/code&gt; command, which you can run to generate the TypeScript file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=./ --ts_proto_opt=nestJs=true ./libs/proto/hybrid.proto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will generate a .ts file in the same directory as &lt;code&gt;hybrid.proto&lt;/code&gt; (my practice is to move the file under &lt;code&gt;libs/types&lt;/code&gt;). The resulting file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
//   protoc-gen-ts_proto  v2.0.4
//   protoc               v5.27.3
// source: shared-resources/proto/hybrid.proto

/* eslint-disable */
import { GrpcMethod, GrpcStreamMethod } from "@nestjs/microservices";
import { Observable } from "rxjs";

export const protobufPackage = "hybrid";

export interface GreetDto {
  greeting: string;
  fullName: string;
}

export interface GreetResponse {
  greet: string;
}

export interface MeetDto {
  name: string;
  surname: string;
  age: number;
}

export interface MeetResponse {
  meet: string;
}

export const HYBRID_PACKAGE_NAME = "hybrid";

export interface HybridAppServiceClient {
  greet(request: GreetDto): Observable&amp;lt;GreetResponse&amp;gt;;

  meet(request: MeetDto): Observable&amp;lt;MeetResponse&amp;gt;;
}

export interface HybridAppServiceController {
  greet(request: GreetDto): Promise&amp;lt;GreetResponse&amp;gt; | Observable&amp;lt;GreetResponse&amp;gt; | GreetResponse;

  meet(request: MeetDto): Promise&amp;lt;MeetResponse&amp;gt; | Observable&amp;lt;MeetResponse&amp;gt; | MeetResponse;
}

export function HybridAppServiceControllerMethods() {
  return function (constructor: Function) {
    const grpcMethods: string[] = ["greet", "meet"];
    for (const method of grpcMethods) {
      const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
      GrpcMethod("HybridAppService", method)(constructor.prototype[method], method, descriptor);
    }
    const grpcStreamMethods: string[] = [];
    for (const method of grpcStreamMethods) {
      const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
      GrpcStreamMethod("HybridAppService", method)(constructor.prototype[method], method, descriptor);
    }
  };
}

export const HYBRID_APP_SERVICE_NAME = "HybridAppService";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below is an example of how to structure your gRPC controller to utilize the TypeScript definitions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Controller, UseFilters } from '@nestjs/common';
import { HybridAppService } from './hybrid-app.service';
import { RpcCustomExceptionFilter } from '@monorepo/utils';
import { GrpcMethod } from '@nestjs/microservices';
import { HybridAppServiceController, HybridAppServiceControllerMethods, HYBRID_APP_SERVICE_NAME } from '@monorepo/types';

@Controller()
@UseFilters(RpcCustomExceptionFilter)
@HybridAppServiceControllerMethods()
export class GrpcHybridAppController implements HybridAppServiceController {
  constructor(private readonly hybridAppService: HybridAppService) {}

  public async greet(dto: { greeting: string; fullName: string }) {
    return this.hybridAppService.greetTheUser(dto);
  }

  public async meet(dto: { name: string; surname: string; age: number }) {
    return this.hybridAppService.meetTheUser(dto);
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;HybridAppServiceController&lt;/code&gt; is an interface that enforces structure on your gRPC controller, ensuring it implements the necessary methods (&lt;code&gt;Greet&lt;/code&gt; and &lt;code&gt;Meet&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;HybridAppServiceControllerMethods&lt;/code&gt; is a decorator that auto-implements boilerplate methods or configurations for the controller, reducing manual setup.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;GrpcMethod&lt;/code&gt; binds a method in your NestJS controller to a specific gRPC method defined in the .proto file.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final step is connecting your gRPC microservice during application bootstrapping. This is straightforward and can be done using &lt;a href="https://docs.nestjs.com/faq/hybrid-application" rel="noopener noreferrer"&gt;NestJS’s hybrid application&lt;/a&gt; support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { AppModule } from './app/app.module';
import { HYBRID_PACKAGE_NAME } from '@monorepo/types';
import { join } from 'path';

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

  // Connect the gRPC microservice
  await app.connectMicroservice({
    transport: Transport.GRPC,
    options: {
      port: '5000',
      protoPath: join(__dirname, '../../libs/proto/hybrid.proto'),
      package: HYBRID_PACKAGE_NAME, // Package name generated from the proto file
      loader: {
        keepCase: true,
      },
    },
  });

  await app.startAllMicroservices();
  await app.listen(3000);
}

bootstrap();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lets not forget the custom error handling of these requests. gRPC relies on status codes and metadata to convey details about errors. Let’s look at an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ArgumentsHost, Catch } from '@nestjs/common';
import { BaseRpcExceptionFilter } from '@nestjs/microservices';
import { Metadata, StatusBuilder, StatusObject } from '@grpc/grpc-js';
import { Status } from '@grpc/grpc-js/build/src/constants';
import { Observable, throwError } from 'rxjs';

// Custom validation exception class
class ValidationException extends Error {
  constructor(public errors: Record&amp;lt;string, string[]&amp;gt;) {
    super('Validation Error');
  }
}

@Catch(ValidationException)
export class RpcValidationExceptionFilter extends BaseRpcExceptionFilter {
  catch(exception: ValidationException, host: ArgumentsHost): Observable&amp;lt;StatusObject&amp;gt; {
    const metadata = new Metadata();
    metadata.add('errors', JSON.stringify(exception.errors));

    const statusBuilder = new StatusBuilder();
    const statusObject = statusBuilder
      .withCode(Status.INVALID_ARGUMENT) 
      .withDetails('Validation failed') 
      .withMetadata(metadata) 
      .build();

    return throwError(() =&amp;gt; statusObject);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there you have it — your hybrid application is now capable of handling both HTTP and gRPC requests, but also managing errors effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing gRPC Endpoints
&lt;/h3&gt;

&lt;p&gt;To test the gRPC endpoints, you can use Postman's &lt;a href="https://learning.postman.com/docs/sending-requests/grpc/grpc-request-interface/" rel="noopener noreferrer"&gt;gRPC client interface&lt;/a&gt;. It provides an easy way to interact with gRPC services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessing &lt;code&gt;hybrid.ts&lt;/code&gt; via &lt;code&gt;@monorepo/types&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If you’re wondering how I do this, it’s thanks to configuring the &lt;code&gt;paths&lt;/code&gt; option in the root &lt;code&gt;tsconfig.base.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"compilerOptions": {
   "paths": {
         "@monorepo/libs": ["libs/types/index.ts"],
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows TypeScript to resolve the path for shared code across the monorepo.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>grpc</category>
      <category>microservices</category>
      <category>nx</category>
    </item>
  </channel>
</rss>
