gRPC provides efficient, type-safe communication between services.
Protocol Buffers Definition#
1// user.proto
2syntax = "proto3";
3
4package user;
5
6service UserService {
7 rpc GetUser(GetUserRequest) returns (User);
8 rpc CreateUser(CreateUserRequest) returns (User);
9 rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
10 rpc StreamUsers(StreamUsersRequest) returns (stream User);
11}
12
13message User {
14 string id = 1;
15 string email = 2;
16 string name = 3;
17 int64 created_at = 4;
18}
19
20message GetUserRequest {
21 string id = 1;
22}
23
24message CreateUserRequest {
25 string email = 1;
26 string name = 2;
27}
28
29message ListUsersRequest {
30 int32 page_size = 1;
31 string page_token = 2;
32}
33
34message ListUsersResponse {
35 repeated User users = 1;
36 string next_page_token = 2;
37}
38
39message StreamUsersRequest {
40 string filter = 1;
41}Server Implementation#
1import * as grpc from '@grpc/grpc-js';
2import * as protoLoader from '@grpc/proto-loader';
3
4const PROTO_PATH = './protos/user.proto';
5
6const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
7 keepCase: true,
8 longs: String,
9 enums: String,
10 defaults: true,
11 oneofs: true,
12});
13
14const userProto = grpc.loadPackageDefinition(packageDefinition).user as any;
15
16// Implement service methods
17const userService = {
18 getUser: async (
19 call: grpc.ServerUnaryCall<GetUserRequest, User>,
20 callback: grpc.sendUnaryData<User>
21 ) => {
22 try {
23 const user = await db.users.findUnique({
24 where: { id: call.request.id },
25 });
26
27 if (!user) {
28 callback({
29 code: grpc.status.NOT_FOUND,
30 message: 'User not found',
31 });
32 return;
33 }
34
35 callback(null, {
36 id: user.id,
37 email: user.email,
38 name: user.name,
39 created_at: user.createdAt.getTime(),
40 });
41 } catch (error) {
42 callback({
43 code: grpc.status.INTERNAL,
44 message: error.message,
45 });
46 }
47 },
48
49 createUser: async (
50 call: grpc.ServerUnaryCall<CreateUserRequest, User>,
51 callback: grpc.sendUnaryData<User>
52 ) => {
53 const user = await db.users.create({
54 data: {
55 email: call.request.email,
56 name: call.request.name,
57 },
58 });
59
60 callback(null, {
61 id: user.id,
62 email: user.email,
63 name: user.name,
64 created_at: user.createdAt.getTime(),
65 });
66 },
67
68 // Server streaming
69 streamUsers: async (
70 call: grpc.ServerWritableStream<StreamUsersRequest, User>
71 ) => {
72 const users = await db.users.findMany({
73 where: call.request.filter ? { name: { contains: call.request.filter } } : {},
74 });
75
76 for (const user of users) {
77 call.write({
78 id: user.id,
79 email: user.email,
80 name: user.name,
81 created_at: user.createdAt.getTime(),
82 });
83 }
84
85 call.end();
86 },
87};
88
89// Start server
90const server = new grpc.Server();
91server.addService(userProto.UserService.service, userService);
92
93server.bindAsync(
94 '0.0.0.0:50051',
95 grpc.ServerCredentials.createInsecure(),
96 (err, port) => {
97 if (err) throw err;
98 console.log(`gRPC server running on port ${port}`);
99 }
100);Client Implementation#
1import * as grpc from '@grpc/grpc-js';
2import * as protoLoader from '@grpc/proto-loader';
3
4const PROTO_PATH = './protos/user.proto';
5
6const packageDefinition = protoLoader.loadSync(PROTO_PATH);
7const userProto = grpc.loadPackageDefinition(packageDefinition).user as any;
8
9// Create client
10const client = new userProto.UserService(
11 'localhost:50051',
12 grpc.credentials.createInsecure()
13);
14
15// Promisify calls
16function getUser(id: string): Promise<User> {
17 return new Promise((resolve, reject) => {
18 client.getUser({ id }, (err: Error, response: User) => {
19 if (err) reject(err);
20 else resolve(response);
21 });
22 });
23}
24
25// Using async iterator for streaming
26async function* streamUsers(filter: string): AsyncGenerator<User> {
27 const call = client.streamUsers({ filter });
28
29 for await (const user of call) {
30 yield user;
31 }
32}
33
34// Usage
35const user = await getUser('123');
36console.log(user);
37
38for await (const user of streamUsers('John')) {
39 console.log('Streamed user:', user);
40}Interceptors (Middleware)#
1// Server interceptor
2const loggingInterceptor: grpc.ServerInterceptor = (
3 methodDescriptor,
4 call
5) => {
6 const start = Date.now();
7 const method = methodDescriptor.path;
8
9 return new grpc.ServerInterceptingCall(call, {
10 start: (metadata, listener, next) => {
11 next(metadata, {
12 onReceiveMessage: (message, next) => {
13 console.log(`[${method}] Request:`, message);
14 next(message);
15 },
16 onReceiveHalfClose: (next) => {
17 next();
18 },
19 });
20 },
21 sendMessage: (message, next) => {
22 console.log(`[${method}] Response:`, message);
23 next(message);
24 },
25 sendStatus: (status, next) => {
26 console.log(`[${method}] Duration: ${Date.now() - start}ms`);
27 next(status);
28 },
29 });
30};
31
32// Client interceptor for auth
33const authInterceptor: grpc.Interceptor = (options, nextCall) => {
34 return new grpc.InterceptingCall(nextCall(options), {
35 start: (metadata, listener, next) => {
36 metadata.set('authorization', `Bearer ${getToken()}`);
37 next(metadata, listener);
38 },
39 });
40};Error Handling#
1// Standard gRPC status codes
2import { status } from '@grpc/grpc-js';
3
4// Server
5callback({
6 code: status.NOT_FOUND,
7 message: 'Resource not found',
8 details: 'User with ID 123 does not exist',
9});
10
11// Client
12try {
13 await getUser('123');
14} catch (error) {
15 if (error.code === status.NOT_FOUND) {
16 console.log('User not found');
17 } else if (error.code === status.UNAVAILABLE) {
18 console.log('Service unavailable, retrying...');
19 }
20}Health Checking#
1// health.proto
2syntax = "proto3";
3
4package grpc.health.v1;
5
6service Health {
7 rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
8 rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
9}
10
11message HealthCheckRequest {
12 string service = 1;
13}
14
15message HealthCheckResponse {
16 enum ServingStatus {
17 UNKNOWN = 0;
18 SERVING = 1;
19 NOT_SERVING = 2;
20 }
21 ServingStatus status = 1;
22}gRPC excels for internal service communication with strong typing and efficient serialization.