gRPCは様々な言語に公式に対応していますが、残念ながらTypeScriptには対応していません。
PureなJavaScriptはIDEの恩恵が十分に得られないなど不満があったので、TypeScriptでgRPCを扱う方法を調査しました。
環境構築手順
前提
Node.jsがインストールされていること
1. プロジェクト作成とパッケージインストール
以下のコマンドを実行し、プロジェクトの作成と必要なパッケージをインストールします。
npm init -y
npm install @grpc/grpc-js google-protobuf @types/google-protobuf typescript ts-node
npm install --save-dev grpc-tools grpc_tools_node_protoc_ts
それぞれのパッケージの概要
- @grpc/grpc-js
- gRPCのJavaScript用パッケージ。以前はgrpcというパッケージで提供されていたらしく、古いサイトやChatGPTはgrpcパッケージを使用したサンプルコードを出してくるので注意。
- grpc-tools
- protoからJavaScriptコードを生成するためのprotocが同梱されています。
- grpc_tools_node_protoc_ts
- protocでTypeScriptを出力するための3rdパーティ製プラグイン。grpc-toolsとgrpc_tools_node_protoc_tsはビルド時のみ必要で実行時には不要なのでdevDpendenciesとしてインストールします。
- google-protobuf
- TimestampやAny型などよく使う型のprotoおよびそれらを表すjsファイルが含まれている(node_modules/google-protobuf/google/protobuf内参照)。また、自動生成されたソースコード内でも参照されています。
- @types/google-protobuf
- 上記の通り、google-protobufはjsファイルで定義されているのでそのままでは型情報がTypeScriptから参照できません。@types/google-protobufをインストールすることで型情報を参照できるようになります。
- ts-node
- TypeScriptをJavaScriptにトランスパイルすることなく直接Node.js上で扱えるようにするライブラリ。
- typescript
- いわずとしれたJavaScriptのスーパーセット。
2. protoファイル作成
プロジェクト直下にprotoフォルダを作成し、greet.protoファイルを作成します。
syntax = "proto3";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
3. package.jsonのscriptsブロックにスクリプト追加
{
"scripts": {
"pregenerate": "rm -rf generate && mkdir generate",
"generate": "grpc_tools_node_protoc --js_out=import_style=commonjs,binary:./generate --grpc_out=grpc_js:./generate --ts_out=grpc_js:./generate -I ./proto ./proto/*.proto",
"start": "ts-node src/server.ts"
}
}
npm run generateコマンドを実行することでprotoファイルからtsファイルおよびjsファイルを自動生成することができます。
pregenerateコマンドを定義することによって、generateスクリプト実行前に生成フォルダの作成も自動で実行されます。
startはサーバーを起動させるスクリプト。後ほど使います。
今回は簡便のためscriptsに直接コマンドを記述しましたが、運用を考えるならMakefileに切り出して、Makefileをscriptsから呼び出すようにしたほうが良いと思います(Makefileならコメントも書けるし、可読性も高く記述できので。メルカリはそうしてるようです)。
4. gRPCサーバ実装
server.tsファイルを作成し、次のコードを追加します。
import * as grpc from '@grpc/grpc-js';
import { GreeterService, IGreeterServer } from '../generate/greeter_grpc_pb';
import { HelloRequest, HelloReply } from '../generate/greeter_pb';
//protoで定義したServiceを実装するクラス定義。今回はサンプルなのでmainと同じファイル内に同居。
class GreeterServer implements IGreeterServer {
[name: string]: grpc.UntypedHandleCall;
sayHello(call: grpc.ServerUnaryCall<HelloRequest, HelloReply>, callback: grpc.sendUnaryData<HelloReply>): void {
const reply = new HelloReply();
reply.setMessage('Hello ' + call.request.getName());
//第一引数がエラーオブジェクト。第二引数がレスポンスオブジェクト。
callback(null, reply);
}
}
//サーバーを起動する。
function main() {
const server = new grpc.Server();
server.addService(GreeterService, new GreeterServer());
//本番運用するときはTLSで通信するべきなので、createInsecure()ではなくcreateSsl()を使う
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
server.start();
console.log('Server started on port 50051');
});
}
main();
5. gRPCクライアント実装
client.tsファイルを作成し、次のコードを追加します。
import * as grpc from '@grpc/grpc-js';
import { GreeterClient } from '../generate/greeter_grpc_pb';
import { HelloRequest } from '../generate/greeter_pb';
function main() {
const client = new GreeterClient('localhost:50051', grpc.credentials.createInsecure());
const request = new HelloRequest();
request.setName('World');
client.sayHello(request, (error, response) => {
if (error) {
console.error(`Error: ${error.message}`);
} else {
console.log(`Greeting: ${response.getMessage()}`);
}
});
}
main();
6. サーバとクライアントのリクエスト
npm run startコマンドでサーバーを起動し、別のターミナルでnpm run clientを実行することで疎通確認ができます。
また、grpcurlなどのツールを使用して疎通することもできます。
grpcurl -plaintext -d '{"name": "hoge"}' -import-path . -proto proto/greet.proto localhost:50051 greet.Greeter/SayHello
実行結果の例:
$ npm run client
> node_grpc_gpt4_3@1.0.0 client
> ts-node src/client.ts
Greeting: Hello hoge
これで、基本的な環境構築はできました。
次回応用編に続きます。
応用編ではメタデータの設定の仕方や、エラーレスポンスの返し方、bytes型の使い方、google-protobufで事前定義されている型の使い方などを書いています。