gRPC & Protocol Buffers
Learn gRPC for high-performance, typed RPC communication with Protocol Buffers
What is gRPC?
gRPC (gRPC Remote Procedure Call) is a high-performance, open-source RPC framework developed by Google. It uses Protocol Buffers (protobuf) for serialization and HTTP/2 for transport, making it significantly faster than REST APIs for many use cases.
gRPC is ideal for microservices communication, real-time streaming, and scenarios where performance and type safety are critical.
⚡ gRPC vs REST
REST
- • JSON/XML - Human readable
- • HTTP/1.1 - Text-based
- • Request/Response only
- • Loosely typed
- • Browser native support
gRPC
- • Protobuf - Binary, compact
- • HTTP/2 - Multiplexed streams
- • Streaming support
- • Strongly typed contracts
- • 10x faster serialization
Protocol Buffers (Protobuf)
Protocol Buffers define the structure of your data and services:
// user.proto - Protocol Buffer definition
syntax = "proto3";
package user;
// Message types (like TypeScript interfaces)
message User {
int32 id = 1;
string name = 2;
string email = 3;
UserRole role = 4;
repeated string tags = 5; // Array
optional string bio = 6; // Optional field
}
enum UserRole {
USER_ROLE_UNSPECIFIED = 0;
USER_ROLE_ADMIN = 1;
USER_ROLE_MEMBER = 2;
}
message GetUserRequest {
int32 id = 1;
}
message GetUsersRequest {
int32 page = 1;
int32 limit = 2;
}
message UserList {
repeated User users = 1;
int32 total = 2;
}
message CreateUserRequest {
string name = 1;
string email = 2;
UserRole role = 3;
}
// Service definition (like REST endpoints)
service UserService {
// Unary RPC (request-response)
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
// Server streaming
rpc ListUsers(GetUsersRequest) returns (stream User);
// Client streaming
rpc UploadUsers(stream User) returns (UserList);
// Bidirectional streaming
rpc Chat(stream Message) returns (stream Message);
}
gRPC Communication Patterns
1. Unary RPC
Single request, single response. Like REST.
Client → Request → Server
Client ← Response ← Server
2. Server Streaming
Single request, stream of responses.
Client → Request → Server
Client ← Response 1 ← Server
Client ← Response 2 ← Server
Client ← Response N ← Server
3. Client Streaming
Stream of requests, single response.
Client → Request 1 → Server
Client → Request 2 → Server
Client → Request N → Server
Client ← Response ← Server
4. Bidirectional Streaming
Both sides stream independently.
Client ↔ Server
(Both send/receive anytime)
gRPC-Web for Browsers
Browsers can't use gRPC directly. Use gRPC-Web with a proxy:
// Install gRPC-Web
// npm install grpc-web
// Generated client from .proto file
import { UserServiceClient } from './generated/user_grpc_web_pb';
import { GetUserRequest, User } from './generated/user_pb';
// Create client (connects to Envoy proxy)
const client = new UserServiceClient('https://api.example.com');
// Unary call
async function getUser(id: number): Promise {
const request = new GetUserRequest();
request.setId(id);
return new Promise((resolve, reject) => {
client.getUser(request, {}, (err, response) => {
if (err) {
reject(err);
} else {
resolve(response);
}
});
});
}
// Server streaming
function listUsers() {
const request = new GetUsersRequest();
request.setPage(1);
request.setLimit(10);
const stream = client.listUsers(request, {});
stream.on('data', (user: User) => {
console.log('Received user:', user.toObject());
});
stream.on('error', (err) => {
console.error('Stream error:', err);
});
stream.on('end', () => {
console.log('Stream ended');
});
}
// Usage
const user = await getUser(123);
console.log(user.getName(), user.getEmail());
Node.js gRPC Server
// server.js - gRPC server in Node.js
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
// Load proto file
const packageDefinition = protoLoader.loadSync('user.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const userProto = grpc.loadPackageDefinition(packageDefinition).user;
// In-memory database
const users = new Map();
let nextId = 1;
// Service implementation
const userService = {
// Unary RPC
getUser(call, callback) {
const user = users.get(call.request.id);
if (user) {
callback(null, user);
} else {
callback({
code: grpc.status.NOT_FOUND,
message: 'User not found'
});
}
},
// Unary RPC
createUser(call, callback) {
const user = {
id: nextId++,
name: call.request.name,
email: call.request.email,
role: call.request.role || 'USER_ROLE_MEMBER'
};
users.set(user.id, user);
callback(null, user);
},
// Server streaming
listUsers(call) {
const { page, limit } = call.request;
const allUsers = Array.from(users.values());
const start = (page - 1) * limit;
const pageUsers = allUsers.slice(start, start + limit);
// Stream each user
for (const user of pageUsers) {
call.write(user);
}
call.end();
},
// Client streaming
uploadUsers(call, callback) {
const uploadedUsers = [];
call.on('data', (user) => {
user.id = nextId++;
users.set(user.id, user);
uploadedUsers.push(user);
});
call.on('end', () => {
callback(null, {
users: uploadedUsers,
total: uploadedUsers.length
});
});
}
};
// Start server
const server = new grpc.Server();
server.addService(userProto.UserService.service, userService);
server.bindAsync(
'0.0.0.0:50051',
grpc.ServerCredentials.createInsecure(),
(err, port) => {
if (err) throw err;
console.log(`gRPC server running on port ${port}`);
server.start();
}
);
Node.js gRPC Client
// client.js - gRPC client in Node.js
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync('user.proto');
const userProto = grpc.loadPackageDefinition(packageDefinition).user;
// Create client
const client = new userProto.UserService(
'localhost:50051',
grpc.credentials.createInsecure()
);
// Promisify for async/await
function promisify(method) {
return (request) => new Promise((resolve, reject) => {
method.call(client, request, (err, response) => {
if (err) reject(err);
else resolve(response);
});
});
}
const getUser = promisify(client.getUser);
const createUser = promisify(client.createUser);
// Usage
async function main() {
// Create user
const newUser = await createUser({
name: 'John Doe',
email: 'john@example.com',
role: 'USER_ROLE_ADMIN'
});
console.log('Created:', newUser);
// Get user
const user = await getUser({ id: newUser.id });
console.log('Retrieved:', user);
// Server streaming
const stream = client.listUsers({ page: 1, limit: 10 });
stream.on('data', (user) => {
console.log('Streamed user:', user);
});
stream.on('end', () => {
console.log('Stream complete');
});
}
main().catch(console.error);
gRPC with Connect (Modern Alternative)
// Connect is a modern gRPC-compatible framework
// Works in browsers without a proxy!
// npm install @connectrpc/connect @connectrpc/connect-web
import { createPromiseClient } from '@connectrpc/connect';
import { createConnectTransport } from '@connectrpc/connect-web';
import { UserService } from './generated/user_connect';
// Create transport
const transport = createConnectTransport({
baseUrl: 'https://api.example.com',
});
// Create typed client
const client = createPromiseClient(UserService, transport);
// Fully typed, async/await friendly!
async function main() {
// Unary call
const user = await client.getUser({ id: 123 });
console.log(user.name, user.email);
// Create user
const newUser = await client.createUser({
name: 'Jane Doe',
email: 'jane@example.com',
role: 'USER_ROLE_MEMBER'
});
// Server streaming
for await (const user of client.listUsers({ page: 1, limit: 10 })) {
console.log('Received:', user.name);
}
}
main();
When to Use gRPC
✅ Use gRPC When
- • Microservices communication
- • High-performance needed
- • Real-time streaming
- • Polyglot environments
- • Strong typing required
- • Mobile apps (bandwidth matters)
⚠️ Consider REST When
- • Public APIs (wider compatibility)
- • Browser-first applications
- • Simple CRUD operations
- • Team unfamiliar with gRPC
- • Need human-readable debugging
💡 gRPC Best Practices
- ✓ Version your protos - Use package versioning (v1, v2)
- ✓ Use field numbers carefully - Never reuse or change them
- ✓ Implement deadlines - Always set timeouts on calls
- ✓ Handle streaming errors - Streams can fail mid-transfer
- ✓ Use gRPC-Web or Connect - For browser compatibility
- ✓ Monitor with interceptors - Add logging and metrics