Help us understand the problem. What is going on with this article?

Node.jsとTypeScriptでgRPCを動かす

More than 1 year has passed since last update.

Node.js上で、gRPCのサーバとクライアントを動かしてみます。

また、gRPCのコード生成の際に、TypeScriptの型情報も出力し、静的な型付けも可能にします。

コード全体はこちらにあります。

TypeScriptのインストール

まずは、TypeScriptをインストールします。

yarn add -D typescript ts-node

ts-node は実行するのに楽なので入れました。普通に tsc でコンパイルしてから node で実行しても良いです。

proto ファイルを書く

proto ファイルに gRPC サービスの定義を書きます。今回は BookService というものを用意しました。

proto/book.proto
syntax = "proto3";

service BookService {
  rpc GetBook(GetBookRequest) returns (GetBookResponse);
}

message GetBookRequest {
  string id = 1;
}

message GetBookResponse {
  Book book = 1;
}

message Book {
  string title = 1;
  string author = 2;
}

proto ファイルからコード生成を行う

proto ファイルから、コード生成を行います。
生成するのは、JavaScriptファイル(.js)とTypeScriptの型定義ファイル(.d.ts)です。

ちなみに、公式のチュートリアルにあるように、Node.jsでgRPCを実装する場合には、実行時にコード生成をおこなう方法(dynamic codegen)と、
事前にprotocで事前にコード生成を行う方法(static codegen)の二種類があります。
今回は、後者の方法をとります。

Node.jsのコード生成をおこなうには、grpc-toolsというnpmパッケージを使用します。
これには、 protoc と その gRPC Node プラグインが同梱されています。

TypeScriptの型定義を生成するには、また別の protoc プラグインである、grpc_tools_node_protoc_ts をインストールします。

yarn add -D grpc-tools grpc_tools_node_protoc_ts

インストールをおこなったら、次のようなシェルスクリプトを用意します。

protoc.sh
#!/usr/bin/env bash

set -eu

export PATH="$PATH:$(yarn bin)"

PROTO_SRC=./proto
PROTO_DEST=./src/proto

mkdir -p ${PROTO_DEST}

grpc_tools_node_protoc \
  --js_out=import_style=commonjs,binary:${PROTO_DEST} \
  --grpc_out=${PROTO_DEST} \
  --plugin=protoc-gen-grpc=$(which grpc_tools_node_protoc_plugin) \
  -I ${PROTO_SRC} \
  ${PROTO_SRC}/*

grpc_tools_node_protoc \
  --plugin=protoc-gen-ts=$(npm bin)/protoc-gen-ts \
  --ts_out=${PROTO_DEST} \
  -I ${PROTO_SRC} \
  ${PROTO_SRC}/*

このスクリプトを実行すると、4つのファイルが生成されます。

src/proto/book_pb.d.ts
src/proto/book_grpc_pb.js
src/proto/book_grpc_pb.d.ts
src/proto/book_pb.js

これでコード生成は完了です。

gRPCサーバの実装

まずは、 grpc パッケージと google-protobuf パッケージをインストールします。

yarn add grpc google-protobuf

サーバのコードは次のようになります。

src/server.ts
import * as grpc from 'grpc';
import * as book_grpc_pb from './proto/book_grpc_pb';
import * as book_pb from './proto/book_pb';

import { bookData } from './books'

class BookService implements book_grpc_pb.IBookServiceServer {
  getBook(
    call: grpc.ServerUnaryCall<book_pb.GetBookRequest>,
    callback: grpc.sendUnaryData<book_pb.GetBookResponse>,
  ) {
    const bookId = call.request.getId();

    const response = new book_pb.GetBookResponse();
    const book = new book_pb.Book();
    book.setTitle(bookData[bookId].title);
    book.setAuthor(bookData[bookId].author);
    response.setBook(book);

    callback(null, response);
  }
}

(() => {
  const server = new grpc.Server();
  server.bind(
    `0.0.0.0:50051`,
    grpc.ServerCredentials.createInsecure(),
  );
  server.addService(
    book_grpc_pb.BookServiceService,
    new BookService(),
  );

  server.start();
})();

先の proto ファイルで定義した BookService を実装したクラスを、TypeScriptで書きます。
BookService には GetBook というメソッドが定義されていました。
なので、インターフェース IBookServiceServer を実装するには、 getBook というメソッドを実装すればよいことになります。

class BookService implements book_grpc_pb.IBookServiceServer {
  getBook(
    call: grpc.ServerUnaryCall<book_pb.GetBookRequest>,
    callback: grpc.sendUnaryData<book_pb.GetBookResponse>,
  ) {
    ...
  }
}

そして、サービスの実装を、gRPCサーバに追加し、起動します。

server.addService(
  book_grpc_pb.BookServiceService,
  new BookService(),
);
server.start();

クライアントの実装

クライアントも実装してみます。

src/client.ts
import * as grpc from 'grpc';
import * as book_grpc_pb from './proto/book_grpc_pb';
import * as book_pb from './proto/book_pb';

const client = new book_grpc_pb.BookServiceClient(
  '127.0.0.1:50051',
  grpc.credentials.createInsecure(),
);

const req = new book_pb.GetBookRequest();
req.setId('book1')

client.getBook(req, function(error, result) {
  if (error) console.log('Error: ', error);
  else console.log(result.toObject());
});

シンプルですね。

実行してみる

サーバを起動します。

yarn ts-node src/server.ts

クライアントからリクエストを送ります。

yarn ts-node src/client.ts

レスポンスが得られれば成功です。

{ book: { title: 'Book1', author: 'Author1' } }
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away