gRPC Microservices

Schema-First Service-to-Service Calls

gRPC Microservices

gRPC is another transport — but unlike TCP, it's schema-first with Protobuf, fast binary encoding, and generated client types.

4 min read Level 3/5 #nestjs#grpc#microservices
What you'll learn
  • Define a .proto file
  • Configure GrpcOptions
  • Generate types and implement handlers

gRPC is to internal service-to-service calls what OpenAPI is to public HTTP APIs — a way to pin down the contract. You write a .proto file, both sides generate code from it, and the wire format is compact binary instead of JSON.

Install

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

Write a .proto

syntax = "proto3";

package users;

service UserService {
  rpc FindOne (UserId) returns (User);
  rpc Create  (CreateUserInput) returns (User);
}

message UserId { int32 id = 1; }
message User { int32 id = 1; string email = 2; string name = 3; }
message CreateUserInput { string email = 1; string name = 2; }

The .proto is the single source of truth. Generate TypeScript types from it with tools like ts-proto or protoc-gen-ts. Both your Nest service and its callers share the same generated types.

The Server

import { join } from 'path';
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';

async function bootstrap() {
  const app = await NestFactory.createMicroservice(AppModule, {
    transport: Transport.GRPC,
    options: {
      package: 'users',
      protoPath: join(__dirname, 'users.proto'),
      url: '0.0.0.0:5000',
    },
  });
  await app.listen();
}
bootstrap();

Handlers are decorated with @GrpcMethod. The arguments name the service and the RPC — they have to match the .proto.

import { Controller } from '@nestjs/common';
import { GrpcMethod } from '@nestjs/microservices';

@Controller()
export class UsersGrpcController {
  constructor(private readonly users: UsersService) {}

  @GrpcMethod('UserService', 'FindOne')
  findOne(data: { id: number }) {
    return this.users.findById(data.id);
  }

  @GrpcMethod('UserService', 'Create')
  create(data: { email: string; name: string }) {
    return this.users.create(data.email, data.name);
  }
}

The Client

Register a Grpc client, then look up the service interface from it:

ClientsModule.register([
  {
    name: 'USERS_PACKAGE',
    transport: Transport.GRPC,
    options: {
      package: 'users',
      protoPath: join(__dirname, '../proto/users.proto'),
      url: 'users:5000',
    },
  },
]);

@Injectable()
export class GatewayService implements OnModuleInit {
  private svc: UserService;

  constructor(@Inject('USERS_PACKAGE') private readonly client: ClientGrpc) {}

  onModuleInit() {
    this.svc = this.client.getService<UserService>('UserService');
  }

  findUser(id: number) {
    return firstValueFrom(this.svc.findOne({ id }));
  }
}

this.svc.findOne({ id }) returns an Observable. gRPC supports four call patterns — unary, server streaming, client streaming, and bidi streaming — and all four are first-class in @nestjs/microservices.

gRPC vs Plain Microservices

gRPC is more setup (the .proto, the codegen) but you get a typed contract, faster encoding, and built-in streaming. For internal service-to-service traffic at any scale, the upfront cost pays for itself quickly.

Server-Sent Events →