NestJS — Enterprise Node.js

gRPC Microservices

16 min Lesson 39 of 48

gRPC Microservices

gRPC is a high-performance, open-source RPC framework developed by Google. It uses Protocol Buffers (protobuf) as its interface definition language and wire format, giving you strongly-typed service contracts, efficient binary serialisation, and native support for bidirectional streaming — all critical for production microservice communication.

Why gRPC instead of REST?

  • Strongly-typed contracts — the .proto file is the single source of truth; both client and server are generated from it, eliminating drift.
  • Binary serialisation — protobuf is 3–10× more compact and faster to parse than JSON.
  • Streaming support — server-side, client-side, and bidirectional streams are first-class citizens.
  • Code generation — TypeScript types are generated automatically, preventing entire classes of runtime errors.

Defining a protobuf service

Everything starts with a .proto file. It declares your messages (data shapes) and the service (the RPC methods). Place it in a shared proto/ directory at the repository root so both the server and client can reference the same file:

// proto/hero.proto syntax = "proto3"; package hero; service HeroService { rpc FindOne (HeroById) returns (Hero); rpc FindAll (Empty) returns (stream Hero); } message HeroById { int32 id = 1; } message Hero { int32 id = 1; string name = 2; } message Empty {}
Field numbers, not names, are used on the wire. Never change an existing field number in a deployed service — it will silently corrupt data from older clients. You may add new fields with new numbers; old clients will ignore unknown fields.

Setting up the gRPC transporter in NestJS

Install the required packages, then configure main.ts to start a microservice with the gRPC transporter:

npm install @nestjs/microservices @grpc/grpc-js @grpc/proto-loader
// main.ts import { NestFactory } from '@nestjs/core'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { join } from 'path'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, { transport: Transport.GRPC, options: { package: 'hero', protoPath: join(__dirname, '../proto/hero.proto'), url: '0.0.0.0:5000', }, }); await app.listen(); } bootstrap();

Implementing gRPC handlers with @GrpcMethod

Use the @GrpcMethod() decorator to map a controller method to an RPC definition. The method name must match the RPC name in the .proto (or be supplied as the second argument):

import { Controller } from '@nestjs/common'; import { GrpcMethod } from '@nestjs/microservices'; interface Hero { id: number; name: string } interface HeroById { id: number } @Controller() export class HeroController { private readonly heroes: Hero[] = [ { id: 1, name: 'Lancelot' }, { id: 2, name: 'Percival' }, ]; // Unary call — one request, one response @GrpcMethod('HeroService', 'FindOne') findOne(data: HeroById): Hero { return this.heroes.find(h => h.id === data.id)!; } }

Unary vs streaming calls

  • Unary — one request, one response (standard HTTP-like). Handled by a plain method returning a value or Promise.
  • Server-streaming — one request, a stream of responses. Return an Observable from the handler; NestJS will emit each value as a separate protobuf message.
  • Client-streaming / Bidirectional — accept an Observable as the first argument. Advanced patterns for real-time scenarios.
import { Observable, of } from 'rxjs'; import { GrpcStreamMethod } from '@nestjs/microservices'; // Server-streaming example @GrpcMethod('HeroService', 'FindAll') findAll(_: {}): Observable<Hero> { return of(...this.heroes); // emits each hero as a separate message }
Use RxJS Observables for streaming. NestJS's gRPC transport integrates directly with RxJS: returning an Observable from a @GrpcMethod handler automatically converts it to a gRPC server stream, keeping your code clean and testable.

Calling a gRPC service from a NestJS client

Inject the gRPC client via @Inject() after registering it as a ClientsModule provider. The client proxy gives you a typed stub matching the .proto interface:

// In the consuming module ClientsModule.register([{ name: 'HERO_PACKAGE', transport: Transport.GRPC, options: { package: 'hero', protoPath: join(__dirname, '../proto/hero.proto'), url: 'localhost:5000', }, }]); // In the consuming service constructor(@Inject('HERO_PACKAGE') private client: ClientGrpc) {} onModuleInit() { this.heroService = this.client.getService<HeroService>('HeroService'); } getHero(id: number): Observable<Hero> { return this.heroService.findOne({ id }); }
Generate TypeScript types from your proto file. Manually writing interfaces that match the proto is error-prone. Use ts-proto or protoc with the TypeScript plugin to generate accurate types and keep them in sync whenever the proto changes.

Summary

gRPC microservices in NestJS follow a clear pattern: define the contract in a .proto file, configure the Transport.GRPC transporter in main.ts, and annotate handlers with @GrpcMethod(). Unary calls return plain values or Promises; server-streaming calls return RxJS Observables. Clients register a ClientsModule provider and call the remote service with full type safety. The protobuf contract ensures both sides speak the same language — even across different programming languages.