protoc-gen-grpc-gateway-ts - Clean, Idiomatic TypeScript for grpc-gateway

protoc-gen-grpc-gateway-ts is a TypeScript client generator for the grpc-gateway project. It generates idiomatic TypeScript clients that connect the web frontend and golang gRPC backend fronted by grpc-gateway.

Background

In Cash App, we prefer gRPC as the way of communication. We also use grpc-gateway to expose our gRPC services to legacy and web clients over HTTP/1.

This worked well until the day we needed to start integrating grpc-gateway with Web clients. To build a Web client for a gRPC enabled service, there are two options.

  1. Use protoc-gen-openapiv2 (previously protoc-gen-swagger) to generate an OpenAPI schema then run an OpenAPI generator to get the Web client

  2. Use grpc-web with envoy grpc-web filter to pipe the request through.

Although both are valid options and have reasonable numbers of users, after some investigation both options have their own pain points.

Pain points

protoc-gen-openapiv2

OpenAPI has both popularity and maturity. However, going down this path means

grpc-web

grpc-web is a Javascript implementation of gPRC for browsers. To use it, we found:

Solution

To solve the pain points found in both protoc-gen-openapiv2 & grpc-web, we build protoc-gen-grpc-gateway-ts. It comes with the following features:

  1. Idiomatic TypeScript clients and messages
  2. Single step - proto -> TypeScript
  3. Supports both unary calls and server side streaming
  4. POJO message construction guarded by message type definitions.
  5. Standard protoc plugin that can be run alongside any other plugins

After the creation of protoc-gen-grpc-gateway-ts, we have

Here is an example of a simple counter service

The proto file:

// file: counter.proto
message Request {
  int32 counter = 1;
}

message Response {
  int32 result = 1;
}

service CounterService {
  rpc Increment(Request) returns (Response);
}

Generated TypeScript file:

// file: counter.pb.ts
import * as fm from "./fetch.pb"

export type Request = {
  counter?: number
}

export type Response = {
  result?: number
}

export class CounterService {
  static Increment(req: UnaryRequest, initReq?: fm.InitReq): Promise<UnaryResponse> {
    return fm.fetchReq<UnaryRequest, UnaryResponse>(`/main.CounterService/Increment`, {...initReq, method: "POST", body: JSON.stringify(req)})
  }
}

Example usage of the generated counter.pb.ts

import {CounterService} from './counter.pb'

// increase the given number once  
async function increase(base: number): Promise<number> {
  const resp = await CounterService.Increase({counter: base})
  return resp.result
} 

Open Sourcing

It was originally planned to be open-sourced via Square/Cash App’s public Github account. However, with the help from Google Engineers, it now lives in an ideal location: grpc-ecosystem, which is next to grpc-gateway.

End Note

It’s a fun and resourceful journey to create protoc-gen-grpc-gateway-ts. Hope it soon will start getting traction and contributions around the globe.