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.
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.