Back to Blog
gRPCAPIProtocol BuffersBackend

gRPC: High-Performance API Communication

Build efficient APIs with gRPC. Learn Protocol Buffers, service definitions, and Node.js implementation.

B
Bootspring Team
Engineering
February 27, 2026
4 min read

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.

Share this article

Help spread the word about Bootspring