Node.js에서 첫 번째 gRPC API
Source: Dev.to

gRPC를 사용하는 이유?
gRPC는 마이크로서비스와 서버리스 아키텍처에서 네이티브로 실행되는 고성능 오픈소스 범용 RPC 프레임워크입니다.
예시
REST API
레스토랑 ID를 기반으로 레스토랑 목록을 반환하는 레스토랑 서비스용 간단한 REST API 예시입니다.
// 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. Express 라우트를 대체하는 gRPC 서버 구현
의존성 설치
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. Express 클라이언트를 대체하는 gRPC 클라이언트 만들기
// 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
r:", err);
return res.status(500).send("Internal Server Error");
}
res.json(response.restaurants);
}
);
});
app.listen(3002, () => {
console.log("User service listening on port 3002");
});
추가 클라이언트 예시
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
| Feature | REST (JSON over HTTP) | gRPC (Protobuf over HTTP/2) |
|---|---|---|
| Transport | HTTP/1.1 | HTTP/2 |
| Payload Format | JSON | Protobuf (binary) |
| Performance | Moderate | High (binary + HTTP/2) |
| Security | TLS/HTTPS | TLS/HTTPS + built‑in auth |
| Scalability | Good | Excellent for high traffic |
| Tooling | Rich ecosystem | Growing but less mature |
| Learning Curve | Low | Higher (proto files, stubs) |
| Flexibility | Very flexible | Less flexible (strict contracts) |
Other Benefits
- Performance: gRPC는 이진 인코딩과 HTTP/2를 사용해 압축 효율이 높고 데이터 전송 속도가 빨라 REST보다 빠릅니다.
- Security: gRPC는 인증 및 권한 부여를 지원해 REST보다 보안성이 높습니다.
- Scalability: gRPC는 높은 트래픽과 대용량 데이터를 처리하도록 설계돼 REST보다 확장성이 좋습니다.
Drawbacks
- Learning Curve: gRPC는 REST보다 학습 곡선이 가파라 배우고 구현하기 어려울 수 있습니다.
- Tooling: gRPC는 REST에 비해 도구 지원이 제한적이라 디버깅 및 테스트가 어려울 수 있습니다.
- Flexibility: gRPC는 REST보다 유연성이 떨어져 요구사항 변화에 대응하기 어려울 수 있습니다.
Conclusion
gRPC는 마이크로서비스를 구축하는 강력하고 효율적인 방법이지만, 모든 상황에 맞는 솔루션은 아닙니다. 고성능, 저지연, 고처리량이 요구되는 애플리케이션에서 특히 빛을 발합니다.
연락 주세요
- Portfolio:
- LinkedIn:
- GitHub:
- YouTube:
