Skip to main content Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/diegosouzapw/awesome-omni-skill --skill grpc-integrationThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... More from this repository Token-efficient tracking for AI orchestration. CLI-first for status updates (~50 tokens), agent fallback for complex ops (~1KB). Use when: updating task status, querying blockers, creating progress files, validating phases.
AshAi extension guidelines for integrating AI capabilities with Ash Framework. Use when implementing vectorization/embeddings, exposing Ash actions as LLM tools, creating prompt-backed actions, or setting up MCP servers. Covers semantic search, LangChain integration, and structured outputs.
This skill should be used when solving hard questions, complex architectural problems, or debugging issues that benefit from GPT-5 Pro or GPT-5.1 thinking models with large file context. Use when standard Claude analysis needs deeper reasoning or extended context windows.
Related occupations SOC
Based on SOC occupation classification
name gRPC Integration description Comprehensive guide to gRPC for high-performance microservices communication with Protocol Buffers.
gRPC Integration
Overview
gRPC (Google Remote Procedure Call) is a high-performance, open-source universal RPC framework that uses Protocol Buffers for serialization. It's ideal for microservices communication, providing efficient binary serialization, streaming capabilities, and built-in code generation.
gRPC Fundamentals and Protocol Buffers
What is gRPC?
gRPC is a modern RPC framework that:
Uses HTTP/2 for transport
Employs Protocol Buffers for efficient serialization
Supports multiple programming languages
Provides built-in streaming (unary, server streaming, client streaming, bidirectional)
Offers code generation from service definitions
Protocol Buffers
Protocol Buffers (protobuf) is a language-agnostic binary serialization format:
syntax = "proto3";
package user;
message User {
string id = 1;
string name = 2;
string email = 3;
repeated string roles = 4;
google.protobuf.Timestamp created_at = 5;
}
Key features:
Binary format (smaller than JSON)
Strong typing
Backward/forward compatible
Efficient serialization/deserialization
Service Definition (.proto Files)
Basic Service Definition
syntax = "proto3";
package user.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
service UserService {
// Unary RPC
rpc GetUser(GetUserRequest) returns (User);
// Server streaming RPC
rpc ListUsers(ListUsersRequest) returns (stream User);
// Client streaming RPC
rpc CreateUserBatch(stream CreateUserRequest) returns (CreateUserBatchResponse);
// Bidirectional streaming RPC
rpc UserEvents(stream UserEventRequest) returns (stream UserEventResponse);
}
message GetUserRequest {
string id = 1;
}
message User {
string id = 1;
string name = 2;
string email = 3;
google.protobuf.Timestamp created_at = 4;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message CreateUserBatchResponse {
repeated string user_ids = 1;
int32 created_count = 2;
}
message UserEventRequest {
string event_type = 1;
bytes payload = 2;
}
message UserEventResponse {
bool acknowledged = 1;
string message = 2;
}
Proto File Organization proto/
├── user/
│ ├── v1/
│ │ ├── user.proto
│ │ └── user_service.proto
│ └── v2/
│ └── user_service.proto
├── common/
│ ├── timestamp.proto
│ └── pagination.proto
└── google/
└── protobuf/
├── timestamp.proto
└── empty.proto
Importing Common Definitions // common/pagination.proto
syntax = "proto3";
package common;
message PageRequest {
int32 page_size = 1;
string page_token = 2;
}
message PageResponse {
string next_page_token = 1;
int32 total_size = 2;
}
// user_service.proto
syntax = "proto3";
package user.v1;
import "common/pagination.proto";
service UserService {
rpc ListUsers(common.PageRequest) returns (ListUsersResponse);
}
message ListUsersResponse {
repeated User users = 1;
common.PageResponse page_info = 2;
}
Unary, Server Streaming, Client Streaming, Bidirectional Streaming
Unary RPC Simplest pattern - client sends request, server sends response:
const client = new UserServiceClient ('localhost:50051' , credentials.createInsecure ());
const request = new GetUserRequest ();
request.setId ('123' );
client.getUser (request, (error, response ) => {
if (error) {
console .error ('Error:' , error);
return ;
}
console .log ('User:' , response.toObject ());
});
function getUser (call, callback ) {
const user = db.getUser (call.request .getId ());
callback (null , user);
}
Server Streaming RPC Client sends request, server returns stream of responses:
const call = client.listUsers (new ListUsersRequest ());
call.on ('data' , (user ) => {
console .log ('User:' , user.toObject ());
});
call.on ('end' , () => {
console .log ('Stream ended' );
});
call.on ('error' , (error ) => {
console .error ('Error:' , error);
});
function listUsers (call ) {
const users = db.listUsers ();
users.forEach (user => {
call.write (user);
});
call.end ();
}
Client Streaming RPC Client sends stream of requests, server returns single response:
const call = client.createUserBatch ((error, response ) => {
if (error) {
console .error ('Error:' , error);
return ;
}
console .log ('Created:' , response.toObject ());
});
const users = [{ name : 'John' , email : 'john@example.com' }, ...];
users.forEach (user => {
const request = new CreateUserRequest ();
request.setName (user.name );
request.setEmail (user.email );
call.write (request);
});
call.end ();
function createUserBatch (call, callback ) {
const userIds = [];
call.on ('data' , (request ) => {
const userId = db.createUser (request.toObject ());
userIds.push (userId);
});
call.on ('end' , () => {
const response = new CreateUserBatchResponse ();
response.setUserIdsList (userIds);
response.setCreatedCount (userIds.length );
callback (null , response);
});
}
Bidirectional Streaming RPC Both client and server send streams:
const call = client.userEvents ();
call.on ('data' , (response ) => {
console .log ('Response:' , response.toObject ());
});
call.on ('end' , () => {
console .log ('Stream ended' );
});
function sendEvent (type, payload ) {
const request = new UserEventRequest ();
request.setEventType (type);
request.setPayload (payload);
call.write (request);
}
function userEvents (call ) {
call.on ('data' , (request ) => {
const response = new UserEventResponse ();
response.setAcknowledged (true );
response.setMessage ('Event received' );
call.write (response);
});
call.on ('end' , () => {
call.end ();
});
}
gRPC vs REST Comparison Aspect gRPC REST Protocol HTTP/2 HTTP/1.1 or HTTP/2 Data Format Protocol Buffers (binary) JSON/XML (text) Payload Size Smaller (binary) Larger (text) Code Generation Built-in Manual or third-party Streaming Native support Limited (SSE) Browser Support Requires gRPC-Web Native Learning Curve Steeper Easier Tooling Mature Very mature Debugging Requires tools Easy (curl, browser) Use Case Internal microservices Public APIs
Internal microservices communication
High-performance requirements
Need for streaming
Strong typing and code generation
Polyglot environments
Public-facing APIs
Browser-based clients
Simple CRUD operations
Need for easy debugging
Legacy system integration
Code Generation (protoc)
Installing protoc
brew install protobuf
apt-get install protobuf-compiler
Generating Node.js Code
npm install @grpc/grpc-js @grpc/proto-loader
protoc --js_out=import_style=commonjs,binary:. --grpc_out=. \
--plugin=protoc-gen-grpc=$(which grpc_tools_node_protoc_plugin) \
user_service.proto
Using @grpc/proto-loader (Dynamic) const grpc = require ('@grpc/grpc-js' );
const protoLoader = require ('@grpc/proto-loader' );
const PROTO_PATH = './proto/user_service.proto' ;
const packageDefinition = protoLoader.loadSync (PROTO_PATH , {
keepCase : true ,
longs : String ,
enums : String ,
defaults : true ,
oneofs : true ,
});
const userProto = grpc.loadPackageDefinition (packageDefinition).user .v1 ;
const client = new userProto.UserService (
'localhost:50051' ,
grpc.credentials .createInsecure ()
);
Generating TypeScript Code
npm install grpc_tools_node_protoc_ts ts-protoc-gen
protoc \
--plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts \
--plugin=protoc-gen-grpc=./node_modules/.bin/grpc_tools_node_protoc_plugin \
--ts_out=./generated \
--grpc_out=./generated \
--proto_path=./proto \
./proto/user_service.proto
Generating Go Code
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
user_service.proto
Generating Python Code
pip install grpcio grpcio-tools
python -m grpc_tools.protoc \
-I./proto \
--python_out=./generated \
--grpc_python_out=./generated \
./proto/user_service.proto
Error Handling and Status Codes
gRPC Status Codes const grpc = require ('@grpc/grpc-js' );
grpc.status .OK
grpc.status .CANCELLED
grpc.status .UNKNOWN
grpc.status .INVALID_ARGUMENT
grpc.status .DEADLINE_EXCEEDED
grpc.status .NOT_FOUND
grpc.status .ALREADY_EXISTS
grpc.status .PERMISSION_DENIED
grpc.status .UNAUTHENTICATED
grpc.status .RESOURCE_EXHAUSTED
grpc.status .FAILED_PRECONDITION
grpc.status .ABORTED
grpc.status .OUT_OF_RANGE
grpc.status .UNIMPLEMENTED
grpc.status .INTERNAL
grpc.status .UNAVAILABLE
grpc.status .DATA_LOSS
Error Handling in Server const grpc = require ('@grpc/grpc-js' );
function getUser (call, callback ) {
try {
const userId = call.request .getId ();
if (!userId) {
const error = new Error ('User ID is required' );
error.code = grpc.status .INVALID_ARGUMENT ;
error.metadata = new grpc.Metadata ();
error.metadata .set ('field' , 'id' );
return callback (error);
}
const user = db.getUser (userId);
if (!user) {
const error = new Error ('User not found' );
error.code = grpc.status .NOT_FOUND ;
return callback (error);
}
callback (null , user);
} catch (error) {
console .error ('Error in getUser:' , error);
const grpcError = new Error ('Internal server error' );
grpcError.code = grpc.status .INTERNAL ;
callback (grpcError);
}
}
Error Handling in Client client.getUser (request, (error, response ) => {
if (error) {
switch (error.code ) {
case grpc.status .NOT_FOUND :
console .error ('User not found' );
break ;
case grpc.status .PERMISSION_DENIED :
console .error ('Permission denied' );
break ;
case grpc.status .DEADLINE_EXCEEDED :
console .error ('Request timeout' );
break ;
default :
console .error ('Error:' , error.message );
}
return ;
}
console .log ('User:' , response.toObject ());
});
Custom Error Metadata
function createUser (call, callback ) {
try {
const { name, email } = call.request .toObject ();
const validationErrors = validateUser ({ name, email });
if (validationErrors.length > 0 ) {
const error = new Error ('Validation failed' );
error.code = grpc.status .INVALID_ARGUMENT ;
error.metadata = new grpc.Metadata ();
error.metadata .set ('validation-errors' , JSON .stringify (validationErrors));
return callback (error);
}
const user = db.createUser ({ name, email });
callback (null , user);
} catch (error) {
callback (error);
}
}
client.createUser (request, (error, response ) => {
if (error && error.code === grpc.status .INVALID_ARGUMENT ) {
const validationErrors = JSON .parse (
error.metadata .get ('validation-errors' )[0 ]
);
console .error ('Validation errors:' , validationErrors);
}
});
Interceptors and Middleware
Server Interceptors const grpc = require ('@grpc/grpc-js' );
const loggingInterceptor = (options, nextCall ) => {
return new grpc.ServerInterceptingCall (nextCall (options), {
start : (metadata, listener, next ) => {
console .log (`[Request] ${options.method_definition.path} ` );
const newListener = {
onReceiveMessage : (message, next ) => {
console .log (`[Message] ${JSON .stringify(message)} ` );
next (message);
},
onReceiveStatus : (status, next ) => {
console .log (`[Status] ${status.code} - ${status.details} ` );
next (status);
},
};
next (metadata, newListener);
},
});
};
const authInterceptor = (options, nextCall ) => {
return new grpc.ServerInterceptingCall (nextCall (options), {
start : (metadata, listener, next ) => {
const token = metadata.get ('authorization' )[0 ];
if (!token) {
const error = {
code : grpc.status .UNAUTHENTICATED ,
details : 'Missing authentication token' ,
};
return next (null , { status : error });
}
const user = verifyToken (token);
if (!user) {
const error = {
code : grpc.status .UNAUTHENTICATED ,
details : 'Invalid token' ,
};
return next (null , { status : error });
}
options.user = user;
next (metadata, listener);
},
});
};
const server = new grpc.Server ();
server.addService (userProto.UserService .service , { getUser, ... });
server.bindAsync (
'0.0.0.0:50051' ,
grpc.ServerCredentials .createInsecure (),
() => {
console .log ('Server started' );
}
);
Client Interceptors
const retryInterceptor = (options, nextCall ) => {
let retryCount = 0 ;
const maxRetries = 3 ;
const newCall = nextCall (options);
const originalStart = newCall.startWithContext ;
newCall.startWithContext = function (metadata, listener, next ) {
const retryListener = {
onReceiveStatus : (status, next ) => {
if (status.code === grpc.status .UNAVAILABLE && retryCount < maxRetries) {
retryCount++;
console .log (`Retrying... (${retryCount} /${maxRetries} )` );
setTimeout (() => {
newCall.startWithContext (metadata, listener, next);
}, 1000 * retryCount);
return ;
}
next (status);
},
};
originalStart.call (this , metadata, retryListener, next);
};
return newCall;
};
const client = new userProto.UserService (
'localhost:50051' ,
grpc.credentials .createInsecure (),
{ interceptors : [retryInterceptor] }
);
Authentication (TLS, JWT, API Keys)
TLS/SSL Configuration const grpc = require ('@grpc/grpc-js' );
const fs = require ('fs' );
const serverCredentials = grpc.ServerCredentials .createSsl (
fs.readFileSync ('ca.crt' ),
[
{
cert_chain : fs.readFileSync ('server.crt' ),
private_key : fs.readFileSync ('server.key' ),
},
],
false
);
server.bindAsync (
'0.0.0.0:50051' ,
serverCredentials,
() => {
console .log ('Secure server started' );
}
);
const clientCredentials = grpc.credentials .createSsl (
fs.readFileSync ('ca.crt' ),
fs.readFileSync ('client.key' ),
fs.readFileSync ('client.crt' )
);
const client = new userProto.UserService (
'localhost:50051' ,
clientCredentials
);
JWT Authentication const jwt = require ('jsonwebtoken' );
const jwtInterceptor = (options, nextCall ) => {
return new grpc.ServerInterceptingCall (nextCall (options), {
start : (metadata, listener, next ) => {
const token = metadata.get ('authorization' )?.[0 ]?.replace ('Bearer ' , '' );
if (!token) {
return next (null , {
status : { code : grpc.status .UNAUTHENTICATED , details : 'Missing token' },
});
}
try {
const decoded = jwt.verify (token, process.env .JWT_SECRET );
options.user = decoded;
next (metadata, listener);
} catch (error) {
return next (null , {
status : { code : grpc.status .UNAUTHENTICATED , details : 'Invalid token' },
});
}
},
});
};
const token = jwt.sign ({ userId : '123' }, process.env .JWT_SECRET );
const metadata = new grpc.Metadata ();
metadata.add ('authorization' , `Bearer ${token} ` );
client.getUser (request, metadata, (error, response ) => {
});
API Key Authentication
const apiKeyInterceptor = (options, nextCall ) => {
return new grpc.ServerInterceptingCall (nextCall (options), {
start : (metadata, listener, next ) => {
const apiKey = metadata.get ('x-api-key' )?.[0 ];
if (!apiKey || !isValidApiKey (apiKey)) {
return next (null , {
status : { code : grpc.status .UNAUTHENTICATED , details : 'Invalid API key' },
});
}
options.apiKey = apiKey;
next (metadata, listener);
},
});
};
function isValidApiKey (apiKey ) {
return process.env .API_KEYS .includes (apiKey);
}
const metadata = new grpc.Metadata ();
metadata.add ('x-api-key' , process.env .API_KEY );
client.getUser (request, metadata, (error, response ) => {
});
Load Balancing and Service Discovery
Client-Side Load Balancing const { RoundRobin } = require ('@grpc/grpc-js/build/src/load-balancer' );
const client = new userProto.UserService (
'dns:///user-service:50051' ,
grpc.credentials .createInsecure (),
{
'grpc.load_balancing_config' : [
{ round_robin : {} },
],
}
);
gRPC Name Resolution
const dns = require ('dns' );
function resolveService (serviceName, callback ) {
dns.resolveSrv (serviceName, (err, addresses ) => {
if (err) {
return callback (err);
}
const targets = addresses.map (addr => ({
host : addr.name ,
port : addr.port ,
}));
callback (null , targets);
});
}
const client = new userProto.UserService (
'custom:///user-service' ,
grpc.credentials .createInsecure ()
);
Service Discovery with Consul const Consul = require ('consul' );
const consul = new Consul ();
async function getServiceAddress (serviceName ) {
const services = await consul.catalog .service .nodes (serviceName);
if (services.length === 0 ) {
throw new Error (`Service ${serviceName} not found` );
}
const service = services[Math .floor (Math .random () * services.length )];
return `${service.ServiceAddress} :${service.ServicePort} ` ;
}
async function createClient (serviceName ) {
const address = await getServiceAddress (serviceName);
return new userProto.UserService (
address,
grpc.credentials .createInsecure ()
);
}
gRPC-Web for Browser Clients
Envoy Proxy Setup
static_resources:
listeners:
- name: grpc_web_listener
address:
socket_address:
address: 0.0 .0 .0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: grpc_web
route_config:
name: local_route
virtual_hosts:
- name: grpc_web_backend
domains: ["*" ]
routes:
- match:
prefix: "/"
route:
cluster: grpc_backend
http_filters:
- name: envoy.filters.http.grpc_web
- name: envoy.filters.http.router
clusters:
- name: grpc_backend
connect_timeout: 5s
type: LOGICAL_DNS
http2_protocol_options: {}
load_assignment:
cluster_name: grpc_backend
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: localhost
port_value: 50051
Browser Client import { grpc } from '@improbable-eng/grpc-web' ;
import { UserServiceClient } from './user_service_pb_service' ;
import { GetUserRequest } from './user_service_pb' ;
const client = new UserServiceClient ('http://localhost:8080' , null , null );
const request = new GetUserRequest ();
request.setId ('123' );
client.getUser (request, {}, (err, response ) => {
if (err) {
console .error ('Error:' , err);
return ;
}
console .log ('User:' , response.toObject ());
});
Health Checking
Standard Health Check Protocol syntax = "proto3";
package grpc.health.v1;
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}
message HealthCheckRequest {
string service = 1;
}
message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0;
SERVING = 1;
NOT_SERVING = 2;
SERVICE_UNKNOWN = 3;
}
ServingStatus status = 1;
}
Implementing Health Check const grpc = require ('@grpc/grpc-js' );
const healthProto = grpc.loadPackageDefinition (
protoLoader.loadSync ('./proto/grpc/health/v1/health.proto' )
).grpc .health .v1 ;
const healthStatus = {
'' : healthProto.HealthCheckResponse .ServingStatus .SERVING ,
'user-service' : healthProto.HealthCheckResponse .ServingStatus .SERVING ,
};
const healthImpl = {
check : (call, callback ) => {
const service = call.request .getService ();
const status = healthStatus[service] || healthProto.HealthCheckResponse .ServingStatus .SERVICE_UNKNOWN ;
const response = new healthProto.HealthCheckResponse ();
response.setStatus (status);
callback (null , response);
},
watch : (call ) => {
const service = call.request .getService ();
const interval = setInterval (() => {
const status = healthStatus[service] || healthProto.HealthCheckResponse .ServingStatus .SERVICE_UNKNOWN ;
const response = new healthProto.HealthCheckResponse ();
response.setStatus (status);
call.write (response);
}, 1000 );
call.on ('cancelled' , () => {
clearInterval (interval);
call.end ();
});
},
};
server.addService (healthProto.Health .service , healthImpl);
Deadlines and Timeouts
Setting Deadlines
const deadline = new Date ();
deadline.setSeconds (deadline.getSeconds () + 5 );
client.getUser (request, { deadline }, (error, response ) => {
if (error && error.code === grpc.status .DEADLINE_EXCEEDED ) {
console .error ('Request timed out' );
}
});
function getUser (call, callback ) {
if (call.getDeadline ().getTime () < Date .now ()) {
const error = new Error ('Deadline exceeded' );
error.code = grpc.status .DEADLINE_EXCEEDED ;
return callback (error);
}
}
Timeout Configuration const client = new userProto.UserService (
'localhost:50051' ,
grpc.credentials .createInsecure (),
{
'grpc.max_receive_message_length' : -1 ,
'grpc.max_send_message_length' : -1 ,
'grpc.initial_reconnect_backoff_ms' : 100 ,
'grpc.max_reconnect_backoff_ms' : 10000 ,
'grpc.keepalive_time_ms' : 60000 ,
'grpc.keepalive_timeout_ms' : 5000 ,
}
);
Node.js Implementation
Complete Server Example const grpc = require ('@grpc/grpc-js' );
const protoLoader = require ('@grpc/proto-loader' );
const { v4 : uuidv4 } = require ('uuid' );
const packageDefinition = protoLoader.loadSync ('./proto/user_service.proto' , {
keepCase : true ,
longs : String ,
enums : String ,
defaults : true ,
oneofs : true ,
});
const userProto = grpc.loadPackageDefinition (packageDefinition).user .v1 ;
const users = new Map ();
const userService = {
getUser : (call, callback ) => {
const user = users.get (call.request .getId ());
if (!user) {
const error = new Error ('User not found' );
error.code = grpc.status .NOT_FOUND ;
return callback (error);
}
callback (null , user);
},
createUser : (call, callback ) => {
const user = {
id : uuidv4 (),
name : call.request .getName (),
email : call.request .getEmail (),
createdAt : new Date (),
};
users.set (user.id , user);
callback (null , user);
},
listUsers : (call ) => {
users.forEach (user => {
call.write (user);
});
call.end ();
},
};
const server = new grpc.Server ();
server.addService (userProto.UserService .service , userService);
server.bindAsync (
'0.0.0.0:50051' ,
grpc.ServerCredentials .createInsecure (),
(error, port ) => {
if (error) {
console .error ('Failed to start server:' , error);
return ;
}
console .log (`Server running on port ${port} ` );
}
);
Complete Client Example const grpc = require ('@grpc/grpc-js' );
const protoLoader = require ('@grpc/proto-loader' );
const packageDefinition = protoLoader.loadSync ('./proto/user_service.proto' , {
keepCase : true ,
longs : String ,
enums : String ,
defaults : true ,
oneofs : true ,
});
const userProto = grpc.loadPackageDefinition (packageDefinition).user .v1 ;
const client = new userProto.UserService (
'localhost:50051' ,
grpc.credentials .createInsecure ()
);
function getUser (id ) {
return new Promise ((resolve, reject ) => {
const request = new userProto.GetUserRequest ();
request.setId (id);
client.getUser (request, (error, response ) => {
if (error) {
return reject (error);
}
resolve (response.toObject ());
});
});
}
function createUser (name, email ) {
return new Promise ((resolve, reject ) => {
const request = new userProto.CreateUserRequest ();
request.setName (name);
request.setEmail (email);
client.createUser (request, (error, response ) => {
if (error) {
return reject (error);
}
resolve (response.toObject ());
});
});
}
function listUsers ( ) {
return new Promise ((resolve, reject ) => {
const request = new userProto.ListUsersRequest ();
const users = [];
const call = client.listUsers (request);
call.on ('data' , (user ) => {
users.push (user.toObject ());
});
call.on ('end' , () => {
resolve (users);
});
call.on ('error' , reject);
});
}
(async () => {
try {
const user = await createUser ('John Doe' , 'john@example.com' );
console .log ('Created user:' , user);
const found = await getUser (user.id );
console .log ('Found user:' , found);
const allUsers = await listUsers ();
console .log ('All users:' , allUsers);
} catch (error) {
console .error ('Error:' , error);
}
})();
Python Implementation
Server Example import grpc
from concurrent import futures
import user_service_pb2
import user_service_pb2_grpc
import uuid
from datetime import datetime
class UserService (user_service_pb2_grpc.UserServiceServicer):
def __init__ (self ):
self .users = {}
def GetUser (self, request, context ):
user_id = request.id
if user_id not in self .users:
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details('User not found' )
return user_service_pb2.User()
user = self .users[user_id]
return user_service_pb2.User(
id =user['id' ],
name=user['name' ],
email=user['email' ],
created_at=datetime_to_timestamp(user['created_at' ])
)
def CreateUser (self, request, context ):
user_id = str (uuid.uuid4())
user = {
'id' : user_id,
'name' : request.name,
'email' : request.email,
'created_at' : datetime.utcnow()
}
self .users[user_id] = user
return user_service_pb2.User(
id =user_id,
name=request.name,
email=request.email,
created_at=datetime_to_timestamp(user['created_at' ])
)
def ListUsers (self, request, context ):
for user in self .users.values():
yield user_service_pb2.User(
id =user['id' ],
name=user['name' ],
email=user['email' ],
created_at=datetime_to_timestamp(user['created_at' ])
)
def datetime_to_timestamp (dt ):
from google.protobuf.timestamp_pb2 import Timestamp
timestamp = Timestamp()
timestamp.FromDatetime(dt)
return timestamp
def serve ():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10 ))
user_service_pb2_grpc.add_UserServiceServicer_to_server(
UserService(), server
)
server.add_insecure_port('[::]:50051' )
server.start()
print ('Server started on port 50051' )
server.wait_for_termination()
if __name__ == '__main__' :
serve()
Client Example import grpc
import user_service_pb2
import user_service_pb2_grpc
def get_user (stub, user_id ):
request = user_service_pb2.GetUserRequest(id =user_id)
response = stub.GetUser(request)
return response
def create_user (stub, name, email ):
request = user_service_pb2.CreateUserRequest(name=name, email=email)
response = stub.CreateUser(request)
return response
def list_users (stub ):
request = user_service_pb2.ListUsersRequest()
for user in stub.ListUsers(request):
print (user)
def main ():
with grpc.insecure_channel('localhost:50051' ) as channel:
stub = user_service_pb2_grpc.UserServiceStub(channel)
user = create_user(stub, 'John Doe' , 'john@example.com' )
print (f'Created user: {user.id } ' )
found = get_user(stub, user.id )
print (f'Found user: {found.name} ' )
print ('All users:' )
list_users(stub)
if __name__ == '__main__' :
main()
Go Implementation
Server Example package main
import (
"context"
"log"
"net"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
pb "path/to/proto/user/v1"
)
type server struct {
pb.UnimplementedUserServiceServer
users map [string ]*pb.User
}
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error ) {
user, ok := s.users[req.Id]
if !ok {
return nil , status.Error(codes.NotFound, "User not found" )
}
return user, nil
}
func (s *server) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error ) {
user := &pb.User{
Id: generateID(),
Name: req.Name,
Email: req.Email,
CreatedAt: timestamppb.Now(),
}
s.users[user.Id] = user
return user, nil
}
func (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
for _, user := range s.users {
if err := stream.Send(user); err != nil {
return err
}
}
return nil
}
func generateID () string {
return "user-" + time.Now().Format("20060102150405" )
}
func main () {
lis, err := net.Listen("tcp" , ":50051" )
if err != nil {
log.Fatalf("Failed to listen: %v" , err)
}
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &server{
users: make (map [string ]*pb.User),
})
log.Println("Server started on port 50051" )
if err := s.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v" , err)
}
}
Client Example package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "path/to/proto/user/v1"
)
func main () {
conn, err := grpc.Dial("localhost:50051" , grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Failed to connect: %v" , err)
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 5 *time.Second)
defer cancel()
createResp, err := client.CreateUser(ctx, &pb.CreateUserRequest{
Name: "John Doe" ,
Email: "john@example.com" ,
})
if err != nil {
log.Fatalf("Failed to create user: %v" , err)
}
log.Printf("Created user: %s" , createResp.Id)
getResp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: createResp.Id})
if err != nil {
log.Fatalf("Failed to get user: %v" , err)
}
log.Printf("Found user: %s" , getResp.Name)
listResp, err := client.ListUsers(ctx, &pb.ListUsersRequest{})
if err != nil {
log.Fatalf("Failed to list users: %v" , err)
}
for {
user, err := listResp.Recv()
if err != nil {
break
}
log.Printf("User: %s (%s)" , user.Name, user.Email)
}
}
Testing gRPC Services
Unit Testing with Node.js const grpc = require ('@grpc/grpc-js' );
const { Server } = require ('@grpc/grpc-js' );
const assert = require ('assert' );
describe ('UserService' , () => {
let server;
let client;
before ((done ) => {
server = new Server ();
server.addService (userProto.UserService .service , userService);
server.bindAsync (
'0.0.0.0:0' ,
grpc.ServerCredentials .createInsecure (),
(error, port ) => {
if (error) return done (error);
client = new userProto.UserService (
`localhost:${port} ` ,
grpc.credentials .createInsecure ()
);
done ();
}
);
});
after ((done ) => {
client.close ();
server.tryShutdown (done);
});
it ('should create and get user' , (done ) => {
const createRequest = new userProto.CreateUserRequest ();
createRequest.setName ('Test User' );
createRequest.setEmail ('test@example.com' );
client.createUser (createRequest, (error, user ) => {
assert.ifError (error);
assert.strictEqual (user.getName (), 'Test User' );
const getRequest = new userProto.GetUserRequest ();
getRequest.setId (user.getId ());
client.getUser (getRequest, (error, foundUser ) => {
assert.ifError (error);
assert.strictEqual (foundUser.getId (), user.getId ());
done ();
});
});
});
});
Best Practices
Schema Design
Use semantic versioning for proto packages
Keep proto files in a separate repository for sharing
Use well-defined common types
Document services and messages with comments
Error Handling
Use appropriate gRPC status codes
Include helpful error messages
Use metadata for additional error context
Log errors server-side
Performance
Use connection pooling
Implement proper timeout/deadline handling
Enable compression for large payloads
Use streaming for large datasets
Security
Always use TLS in production
Implement proper authentication
Use mutual TLS for service-to-service communication
Validate all inputs
Observability
Implement structured logging
Use metrics for monitoring
Enable tracing with OpenTelemetry
Set up health checks
Production Checklist
Related Skills
09-microservices/service-design
09-microservices/service-discovery