你的第一个 gRPC API 在 Node.js 中
发布: (2026年2月2日 GMT+8 13:08)
6 min read
原文: Dev.to
Source: Dev.to

为什么使用 gRPC?
gRPC 是一个高性能、开源的通用 RPC 框架,可在微服务和无服务器架构上原生运行。
示例
Rest API
一个简单的餐厅服务 REST API 示例,根据餐厅 ID 返回餐厅列表。
// restaurant-service
import express from "express";
const app = express();
app.use(express.json());
const restaurantsData = [
{
id: "res_001",
name: "Spicy Garden",
cuisine: "Indian",
location: "New York",
rating: 4.5,
isOpen: true,
},
{
id: "res_002",
name: "Sushi Zen",
cuisine: "Japanese",
location: "San Francisco",
rating: 4.7,
isOpen: false,
},
{
id: "res_003",
name: "Pasta Palace",
cuisine: "Italian",
location: "Chicago",
rating: 4.2,
isOpen: true,
},
{
id: "res_004",
name: "Burger Hub",
cuisine: "American",
location: "Austin",
rating: 4.0,
isOpen: true,
},
{
id: "res_005",
name: "Dragon Wok",
cuisine: "Chinese",
location: "Seattle",
rating: 4.6,
isOpen: false,
},
];
app.post("/restaurants/bulk", (req, res) => {
const { restaurantIds } = req.body;
const result = restaurantsData.filter((r) =>
restaurantIds.includes(r.id)
);
res.json(result);
});
app.listen(3001, () => {
console.log("Server is running on port 3001");
});
将此 REST API 转换为 gRPC
1. 定义 .proto 文件
.proto 文件是一种语言无关、平台无关、协议无关的接口定义语言(IDL),用于描述服务和消息格式。
查看文档了解更多细节:
// restaurants.proto
syntax = "proto3";
package restaurants;
service RestaurantService {
rpc GetRestaurantsBulk (BulkRestaurantRequest) returns (BulkRestaurantResponse);
}
message BulkRestaurantRequest {
repeated string restaurantIds = 1;
}
message Restaurant {
string id = 1;
string name = 2;
string cuisine = 3;
string location = 4;
double rating = 5;
bool isOpen = 6;
}
message BulkRestaurantResponse {
repeated Restaurant restaurants = 1;
}
2. 实现一个 gRPC 服务器 来替代 Express 路由
安装依赖
bun add @grpc/grpc-js @grpc/proto-loader
服务器代码 (grpc-server.js)
// restaurant-service/grpc-server.js
import grpc from "@grpc/grpc-js";
import protoLoader from "@grpc/proto-loader";
import path from "path";
const PROTO_PATH = path.resolve("restaurants.proto");
// Load proto definition
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const restaurantProto = grpc.loadPackageDefinition(packageDefinition).restaurants;
// Mock data (same as Express)
const restaurantsData = [
{
id: "res_001",
name: "Spicy Garden",
cuisine: "Indian",
location: "New York",
rating: 4.5,
isOpen: true,
},
{
id: "res_002",
name: "Sushi Zen",
cuisine: "Japanese",
location: "San Francisco",
rating: 4.7,
isOpen: false,
},
{
id: "res_003",
name: "Pasta Palace",
cuisine: "Italian",
location: "Chicago",
rating: 4.2,
isOpen: true,
},
{
id: "res_004",
name: "Burger Hub",
cuisine: "American",
location: "Austin",
rating: 4.0,
isOpen: true,
},
{
id: "res_005",
name: "Dragon Wok",
cuisine: "Chinese",
location: "Seattle",
rating: 4.6,
isOpen: false,
},
];
// gRPC method implementation
function getRestaurantsBulk(call, callback) {
const { restaurantIds } = call.request;
const restaurants = restaurantsData.filter((r) =>
restaurantIds.includes(r.id)
);
callback(null, { restaurants });
}
// Create and start the server
const server = new grpc.Server();
server.addService(restaurantProto.RestaurantService.service, {
GetRestaurantsBulk: getRestaurantsBulk,
});
server.bindAsync(
"0.0.0.0:50051",
grpc.ServerCredentials.createInsecure(),
(port, error) => {
if (error) {
console.error("Error binding server:", error);
return;
}
console.log("gRPC Server running on port", port);
server.start();
}
);
3. 创建一个 gRPC 客户端 来替代 Express 客户端
// user-service/grpc-client.ts
import express from "express";
import grpc from "@grpc/grpc-js";
import protoLoader from "@grpc/proto-loader";
import path from "path";
const app = express();
// Load proto definition
const PROTO_PATH = path.resolve("restaurants.proto");
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const restaurantProto: any = grpc.loadPackageDefinition(packageDefinition).restaurants;
// Create a client that talks to the gRPC server
const restaurantClient = new restaurantProto.RestaurantService(
"localhost:50051",
grpc.credentials.createInsecure()
);
// Example endpoint that uses the gRPC client
app.get("/my-visited-restaurants", (req, res) => {
// In a real scenario, you would get these IDs from the user's history
const visitedIds = ["res_001", "res_003", "res_005"];
restaurantClient.GetRestaurantsBulk(
{ restaurantIds: visitedIds },
(err: any, response: any) => {
if (err) {
console.error("gRPC erro
#### 附加客户端示例
```ts
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import express from 'express';
const app = express();
const restaurantProtoPath = __dirname + '/proto/restaurant.proto';
const restaurantPackageDefinition = protoLoader.loadSync(restaurantProtoPath, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const restaurantProto: any = grpc.loadPackageDefinition(restaurantPackageDefinition).restaurant;
const restaurantClient = new restaurantProto.RestaurantService(
'localhost:50051',
grpc.credentials.createInsecure()
);
const userData = [
{
id: 'user_001',
name: 'John Doe',
email: 'john.doe@example.com',
vistedRestaurant: ['res_001', 'res_002', 'res_003'],
},
];
app.get('/users/:id/visit-restaurants', async (req, res) => {
const userId = req.params.id;
const userDetailData = userData.find((user) => user.id === userId);
restaurantClient.GetRestaurantsBulk(
{
restaurantIds: userDetailData?.vistedRestaurant,
},
(err: grpc.ServiceError | null, response: any) => {
if (err) {
console.error(err);
return res.status(500).json({ message: 'gRPC error' });
}
res.json({
visitedRestaurantsDetail: response.restaurants,
});
}
);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
通过 .proto 定义、gRPC 服务器和匹配的客户端,你已经将一个简单的 REST 端点转换为高性能的 gRPC 服务。 🎉
Comparison: Express (REST) vs. gRPC
| 特性 | REST (JSON over HTTP) | gRPC (Protobuf over HTTP/2) |
|---|---|---|
| 传输层 | HTTP/1.1 | HTTP/2 |
| 负载格式 | JSON | Protobuf(二进制) |
| 性能 | 中等 | 高(二进制 + HTTP/2) |
| 安全性 | TLS/HTTPS | TLS/HTTPS + 内置认证 |
| 可扩展性 | 良好 | 在高流量下表现卓越 |
| 工具链 | 生态系统丰富 | 正在成长但尚不成熟 |
| 学习曲线 | 低 | 较高(proto 文件、桩代码) |
| 灵活性 | 非常灵活 | 灵活性较低(严格的契约) |
其他优势
- 性能:gRPC 比 REST 更快,因为它使用二进制编码和 HTTP/2,能够实现更好的压缩和更快的数据传输。
- 安全性:gRPC 支持身份验证和授权,安全性高于 REST。
- 可扩展性:gRPC 设计用于处理高流量和大数据集,因而比 REST 更具可扩展性。
缺点
- 学习曲线:gRPC 的学习曲线比 REST 陡峭,学习和实现难度更大。
- 工具链:相较于 REST,gRPC 的工具支持有限,调试和测试更具挑战性。
- 灵活性:gRPC 的灵活性低于 REST,适应需求变化的难度更大。
结论
gRPC 是构建微服务的强大且高效的方式,但它并非万用解决方案。它在高性能、低延迟和高吞吐量的应用场景中表现出色。
请保持联系
- 作品集:
- 领英:
- GitHub:
- YouTube:
