gRPC-WebをNext.jsで動かす

gRPC-Webを触っててNext.js上で動かしてみたかったがあまり事例がなく、動かすまでに四苦八苦したのでメモ。

gRPC-Webは有名な実装が2種類あった。
今回はNext.jsで動かすので、Node.jsもサポートされている方を使います。

GitHub - improbable-eng/grpc-web: gRPC Web implementation for Golang and TypeScript

protoの定義などはいろんな場所で解説されているので、ハマりどころを中心にまとめていきます。

protocのjs用プラグインのビルド

protocからJSのコードを生成したいんですが、grpc/grpc-webリポジトリには以下のような注意書きがあります。

NOTE: Javascript output is no longer supported by protocolbuffers/protobuf package as it previously did. Please use the releases from protocolbuffers/protobuf-javascript instead.

GitHub - grpc/grpc-web: gRPC for Web Clients

残念ながらJS出力用のプラグインはサポートされていないので用意する必要があります。
プラグインのリポジトリを除くとバイナリが配布されていますが M1 Mac用のビルドはありません。自力でビルドする必要があります。

issueを探してみるとbazelでビルドする方法が紹介されていました。

Generate js file from proto file with protoc v21.1 · Issue #127 · protocolbuffers/protobuf-javascript · GitHub

$ git clone https://github.com/protocolbuffers/protobuf-javascript
$ cd protobuf-javascript/
$ bazel build //generator:protoc-gen-js

ビルドしたプラグインを使ってJSのコードを生成します。

PROTOC_GEN_JS_PATH="./bin/protoc-gen-js"
OUT_DIR="./src/generated"

gen_protoc:
    protoc \
        --plugin="protoc-gen-ts=./node_modules/.bin/protoc-gen-ts" \
        --plugin=${PROTOC_GEN_JS_PATH} \
        --proto_path=../protos \
        ../protos/*.proto \
        --js_out=import_style=commonjs,binary:${OUT_DIR} \
        --ts_out="service=grpc-web:${OUT_DIR}"

.PHONY: gen_protoc

ちなみにプロジェクトのディレクトリ構造はこんな感じ。

サーバー側とprotoc定義を共有するため、1リポジトリですべてのコードを管理しており、1つ上の階層にproto置き場を作っています。
--proto_path でproto置き場を指定してるにも関わらず ..protos/.proto と相対パスで指定する必要があるのがくせ者。

$ tree -L 2
.
├── Makefile
├── bin
│   └── protoc-gen-js // ビルドしたJS用プラグイン
├── next-env.d.ts
├── node_modules
├── package-lock.json
├── package.json
├── pages
│   └── index.tsx
├── src
│   └── generated  // protocから生成したコード置き場
└── tsconfig.json

proxyの導入

grpc/grpc-webではenvoyを使ったproxyの導入が必要ですが、improbable-eng/grpc-webにはGoのプロキシが用意されています。
幸いこちらはmacos用のビルドが配布されているので、それをDLして使います。

Releases · improbable-eng/grpc-web · GitHub

今回はローカルで使うので、CORSやTLSに関する設定はゆるゆるで。

PROXY_BIN_PATH="./bin/grpcwebproxy-v0.15.0-osx-x86_64"

${PROXY_BIN_PATH} \
    --backend_addr=localhost:50051 \
    --run_tls_server=false \
    --allow_all_origins

ここまでやってクライアントからリクエストすると、ブラウザでgRPCなサーバーとやりとりできるNext.js製アプリケーションが動くはずです。