Node.js에서 첫 번째 gRPC API

발행: (2026년 2월 2일 오후 02:08 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

Your first gRPC API in Node.js에 대한 커버 이미지

Cheulong Sear

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

FeatureREST (JSON over HTTP)gRPC (Protobuf over HTTP/2)
TransportHTTP/1.1HTTP/2
Payload FormatJSONProtobuf (binary)
PerformanceModerateHigh (binary + HTTP/2)
SecurityTLS/HTTPSTLS/HTTPS + built‑in auth
ScalabilityGoodExcellent for high traffic
ToolingRich ecosystemGrowing but less mature
Learning CurveLowHigher (proto files, stubs)
FlexibilityVery flexibleLess 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:
Back to Blog

관련 글

더 보기 »

Koa에 대한 Zero-configuration 지원

Vercel은 이제 애플리케이션을 지원합니다. 이는 웹 애플리케이션과 API를 더 즐겁게 작성할 수 있게 해주는 표현력 있는 HTTP 미들웨어 프레임워크이며, 설정이 필요 없습니다. Koa...