gRPC with Node.js — Complete Tutorial
Advertisement
gRPC is Google's RPC framework for building distributed systems. It uses Protocol Buffers and HTTP/2 for blazing-fast communication between services.
What is gRPC?
- Protocol Buffers: Binary serialization format
- HTTP/2: Multiplexed connections
- Performance: 7x faster than REST
- Streaming: Built-in request/response streaming
- Language-agnostic: Generate code in any language
Setup
npm install @grpc/grpc-js @grpc/proto-loader
npm install --save-dev protobufjs
Defining Services with .proto Files
// user.proto
syntax = "proto3";
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(Empty) returns (stream User);
rpc CreateUser(CreateUserRequest) returns (User);
rpc UpdateUser(UpdateUserRequest) returns (User);
}
message User {
string id = 1;
string name = 2;
string email = 3;
}
message GetUserRequest {
string id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message UpdateUserRequest {
string id = 1;
string name = 2;
string email = 3;
}
message Empty {}
Server Implementation
import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
const packageDefinition = protoLoader.loadSync("user.proto");
const userProto = grpc.loadPackageDefinition(packageDefinition);
interface User {
id: string;
name: string;
email: string;
}
const users: User[] = [
{ id: "1", name: "Alice", email: "alice@example.com" },
{ id: "2", name: "Bob", email: "bob@example.com" },
];
const userServiceImpl = {
getUser: (call: any, callback: any) => {
const user = users.find((u) => u.id === call.request.id);
if (!user) {
return callback(new Error("User not found"));
}
callback(null, user);
},
listUsers: (call: any) => {
users.forEach((user) => {
call.write(user);
});
call.end();
},
createUser: (call: any, callback: any) => {
const newUser = {
id: Date.now().toString(),
name: call.request.name,
email: call.request.email,
};
users.push(newUser);
callback(null, newUser);
},
updateUser: (call: any, callback: any) => {
const user = users.find((u) => u.id === call.request.id);
if (!user) {
return callback(new Error("User not found"));
}
user.name = call.request.name || user.name;
user.email = call.request.email || user.email;
callback(null, user);
},
};
const server = new grpc.Server();
server.addService(userProto.UserService.service, userServiceImpl);
server.bindAsync(
"127.0.0.1:50051",
grpc.ServerCredentials.createInsecure(),
(err, port) => {
if (err) throw err;
console.log(`gRPC server running on port ${port}`);
server.start();
}
);
- What is gRPC?
- Setup
- Defining Services with .proto Files
- Server Implementation
- Client Implementation
- Streaming
- Error Handling
- Metadata (Headers)
- REST Gateway (Connect)
- Performance Comparison
- FAQ
Client Implementation
import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
const packageDefinition = protoLoader.loadSync("user.proto");
const userProto = grpc.loadPackageDefinition(packageDefinition);
const client = new (userProto as any).UserService(
"localhost:50051",
grpc.credentials.createInsecure()
);
// Unary RPC
client.getUser({ id: "1" }, (err: any, response: any) => {
if (err) {
console.error(err);
} else {
console.log("User:", response);
}
});
// Server streaming
const call = client.listUsers({});
call.on("data", (user: any) => {
console.log("User:", user);
});
call.on("end", () => {
console.log("Stream ended");
});
// Unary request
client.createUser(
{ name: "Charlie", email: "charlie@example.com" },
(err: any, response: any) => {
if (err) {
console.error(err);
} else {
console.log("Created user:", response);
}
}
);
Streaming
# Bidirectional streaming
service ChatService {
rpc Chat(stream Message) returns (stream Message);
}
message Message {
string sender = 1;
string text = 2;
}
// Server-side bidirectional streaming
const chatServiceImpl = {
chat: (call: any) => {
call.on("data", (message: any) => {
console.log(`${message.sender}: ${message.text}`);
// Echo back
call.write({
sender: "Server",
text: `Echo: ${message.text}`,
});
});
call.on("end", () => {
call.end();
});
},
};
// Client-side bidirectional streaming
const call = client.chat();
call.write({ sender: "Client", text: "Hello" });
call.write({ sender: "Client", text: "How are you?" });
call.on("data", (response: any) => {
console.log(`${response.sender}: ${response.text}`);
});
call.on("end", () => {
console.log("Chat ended");
});
call.end();
Error Handling
client.getUser({ id: "999" }, (err: any, response: any) => {
if (err) {
console.error(`Error code: ${err.code}`);
console.error(`Error message: ${err.message}`);
switch (err.code) {
case grpc.status.NOT_FOUND:
console.error("User not found");
break;
case grpc.status.INTERNAL:
console.error("Internal server error");
break;
case grpc.status.UNAUTHENTICATED:
console.error("Not authenticated");
break;
}
}
});
Metadata (Headers)
// Add metadata to request
const metadata = new grpc.Metadata();
metadata.add("authorization", "Bearer token123");
client.getUser({ id: "1" }, metadata, (err: any, response: any) => {
console.log(response);
});
// Server-side: read metadata
const getUser = (call: any, callback: any) => {
const auth = call.metadata.get("authorization")[0];
console.log(`Authorization: ${auth}`);
callback(null, { id: "1", name: "Alice", email: "alice@example.com" });
};
REST Gateway (Connect)
Use gRPC with REST clients:
import { ConnectRouter } from "@connectrpc/connect";
export const routes = (router: ConnectRouter) => {
router.rpc(UserService, UserService.methods.getUser, getUser);
router.rpc(UserService, UserService.methods.createUser, createUser);
};
// Accessible via HTTP:
// POST http://localhost:8080/user.UserService/GetUser
// {"id": "1"}
Performance Comparison
| Metric | REST | gRPC |
|---|---|---|
| Latency | ~100ms | ~14ms |
| Throughput | 100k req/s | 700k req/s |
| Message Size | 500KB | 70KB |
| Protocol | HTTP/1.1 | HTTP/2 |
FAQ
Q: Should I use gRPC for public APIs? A: gRPC is better for service-to-service. For public APIs, REST or GraphQL are more accessible since they use HTTP/1.1.
Q: Can I use gRPC with browsers? A: Yes, with gRPC-Web which runs over HTTP/1.1. gRPC natively requires HTTP/2.
Q: Is gRPC faster than REST? A: Yes, significantly. gRPC uses binary protocols and multiplexing.
gRPC is the modern choice for building high-performance microservices. REST remains better for public APIs, but for internal service communication, gRPC's speed and efficiency are unbeatable.
Advertisement