| name | grpc-api |
| description | Build high-performance gRPC services with Protocol Buffers, streaming patterns, and microservice communication in Node.js and polyglot environments. Use when the task involves `gRPC server or client`, `Protocol Buffers`, `protobuf service definition`, `gRPC streaming`, `microservice communication`, or `inter-service APIs`. |
| license | MIT |
| metadata | {"version":"1.0.0"} |
gRPC API Development
Build efficient gRPC services using Protocol Buffers for contract-first API design, with support for
unary calls, server streaming, client streaming, and bidirectional streaming.
When to Use
- Building microservices that require high-performance binary communication.
- Defining strict service contracts with Protocol Buffers (
.proto files).
- Implementing real-time bidirectional or server-push streaming.
- Creating internal service-to-service APIs in polyglot architectures.
- Optimizing bandwidth in constrained or high-throughput environments.
Critical Patterns
- Contract First: Always define your
.proto file before writing any server or client code. The
proto IS your API contract.
- Use Proper Status Codes: Map domain errors to gRPC status codes (
NOT_FOUND,
ALREADY_EXISTS, INVALID_ARGUMENT, etc.). Never return raw exceptions.
- Field Numbering is Forever: Once a proto field number is assigned and released, NEVER reuse
it. Add new fields with new numbers; deprecate old ones.
- Streaming for Large Data: Use server streaming for large result sets and client streaming for
bulk uploads. Avoid sending massive unary payloads.
- Deadlines Over Timeouts: Always set deadlines on client calls. A missing deadline can hang
forever in production.
- TLS in Production: Never use
createInsecure() credentials outside of local development.
- Keep Messages Flat: Avoid deeply nested message types. Flatten where possible for better wire
efficiency and readability.
Proto Definition Patterns
Complete Service Definition
syntax = "proto3";
package user.service;
message User {
string id = 1;
string email = 2;
string first_name = 3;
string last_name = 4;
string role = 5;
int64 created_at = 6;
int64 updated_at = 7;
}
message CreateUserRequest {
string email = 1;
string first_name = 2;
string last_name = 3;
string role = 4;
}
message UpdateUserRequest {
string id = 1;
string email = 2;
string first_name = 3;
string last_name = 4;
}
message GetUserRequest {
string id = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 limit = 2;
}
message ListUsersResponse {
repeated User users = 1;
int32 total = 2;
int32 page = 3;
}
message DeleteUserRequest {
string id = 1;
}
message Empty {}
// Four RPC patterns: unary, server streaming, client streaming, bidirectional
service UserService {
rpc GetUser(GetUserRequest) returns (User); // Unary
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse); // Unary
rpc CreateUser(CreateUserRequest) returns (User); // Unary
rpc UpdateUser(UpdateUserRequest) returns (User); // Unary
rpc DeleteUser(DeleteUserRequest) returns (Empty); // Unary
rpc StreamUsers(Empty) returns (stream User); // Server streaming
rpc BulkCreateUsers(stream CreateUserRequest) returns (ListUsersResponse); // Client streaming
}
Event Streaming Service
message Event {
string type = 1;
string user_id = 2;
string data = 3;
int64 timestamp = 4;
}
service EventService {
rpc Subscribe(Empty) returns (stream Event); // Server streaming
rpc PublishEvent(Event) returns (Empty); // Unary
}
Node.js Server Implementation
Loading Proto and Implementing Handlers
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const path = require("path");
const packageDef = protoLoader.loadSync(path.join(__dirname, "user.proto"), {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const userProto = grpc.loadPackageDefinition(packageDef).user.service;
const users = new Map();
let userIdCounter = 1;
const userServiceImpl = {
getUser: (call, callback) => {
const user = users.get(call.request.id);
if (!user) {
return callback({
code: grpc.status.NOT_FOUND,
details: "User not found",
});
}
callback(, user);
},
: {
page = call.. || ;
limit = call.. || ;
offset = (page - ) * limit;
userArray = .(users.());
paginatedUsers = userArray.(offset, offset + limit);
(, {
: paginatedUsers,
: userArray.,
: page,
});
},
: {
id = (userIdCounter++);
user = {
id,
: call..,
: call..,
: call..,
: call..,
: .(),
: .(),
};
users.(id, user);
(, user);
},
: {
.(users.()).( {
call.(user);
});
call.();
},
: {
createdUsers = [];
call.(, {
id = (userIdCounter++);
user = {
id,
: request.,
: request.,
: request.,
: request.,
: .(),
: .(),
};
users.(id, user);
createdUsers.(user);
});
call.(, {
(, {
: createdUsers,
: createdUsers.,
: ,
});
});
call.(, {
(err);
});
},
};
server = grpc.();
server.(userProto.., userServiceImpl);
server.(
,
grpc..(),
{
.();
},
);
Client Implementation
Unary, Server Streaming, and Client Streaming Calls
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const path = require("path");
const packageDef = protoLoader.loadSync(path.join(__dirname, "user.proto"));
const userProto = grpc.loadPackageDefinition(packageDef).user.service;
const client = new userProto.UserService(
"localhost:50051",
grpc.credentials.createInsecure(),
);
client.getUser({ id: "123" }, (err, user) => {
if (err) console.error(err);
console.log("User:", user);
});
const stream = client.streamUsers({});
stream.on("data", (user) => {
console.log("Received user:", user);
});
stream.on("end", () => {
console.();
});
writeStream = client.( {
(err) .(err);
.(, response..);
});
writeStream.({
: ,
: ,
: ,
});
writeStream.({
: ,
: ,
: ,
});
writeStream.();
Client with Deadlines and Metadata
const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 5);
const metadata = new grpc.Metadata();
metadata.add("x-request-id", "abc-123");
metadata.add("authorization", "Bearer token-here");
client.getUser({ id: "123" }, metadata, { deadline }, (err, user) => {
if (err) {
if (err.code === grpc.status.DEADLINE_EXCEEDED) {
console.error("Request timed out");
}
return;
}
console.log("User:", user);
});
gRPC Status Codes Reference
| Code | Name | When to Use |
|---|
| 0 | OK | Success |
| 3 | INVALID_ARGUMENT | Bad input from client |
| 5 | NOT_FOUND | Resource does not exist |
| 6 | ALREADY_EXISTS | Duplicate creation attempt |
| 7 | PERMISSION_DENIED | Authenticated but not authorized |
| 13 | INTERNAL | Unexpected server error |
| 14 | UNAVAILABLE | Transient failure, client should retry |
| 16 | UNAUTHENTICATED | Missing or invalid credentials |
Best Practices
✅ DO
- Define
.proto files first — they are your single source of truth.
- Use clear, descriptive message and service naming (
CreateUserRequest, not Req1).
- Implement proper error handling with gRPC status codes on every handler.
- Add metadata for request tracing (request ID, correlation ID).
- Version your protobuf definitions — never break backwards compatibility.
- Use server streaming for large datasets instead of massive unary responses.
- Set deadlines on every client call.
- Use TLS credentials in production (
grpc.ServerCredentials.createSsl()).
- Monitor gRPC metrics (latency, error rates, stream counts).
❌ DON'T
- Use gRPC directly from browsers — use gRPC-Web or a REST gateway instead.
- Reuse or reassign proto field numbers after they have been published.
- Create deeply nested message types — keep messages flat and composable.
- Ignore error status codes or return generic
INTERNAL for all failures.
- Send uncompressed large payloads — enable gzip with
grpc.compression.
- Skip TLS in production — always encrypt service-to-service traffic.
- Use
createInsecure() credentials outside of local development.
- Expose internal implementation details in proto definitions.