When you want to access gRPC services from browsers, you need to implement gRPC-Web clients. Browsers’ fetch lacks several functionalities to implement full spec gRPC for years.
While gRPC and protobuf was initially led by Google, protobuf-javascript is not maintained actively.
At this time, ConnectRPC’s web client and protobuf-es set is more active.
Stub generation
Client stub can be generated with
buf generate.
You can install buf CLI and plugins with npm or pnpm.
npm install --save-dev @bufbuild/buf @bufbuild/protoc-gen-es @connectrpc/protoc-connect-gen-es
buf generate sub command refers to buf.gen.yaml config, which should be placed on the top directory including your .proto files:
version: v1
plugins:
# for @bufbuild/protoc-gen-es
- plugin: es
out: src/
# for @connectrpc/protoc-connect-gen-es
- plugin: connect-es
out: src/
Client code
ConnectRPC can change protocols between ConnectRPC, gRPC and gRPC-web by switching transports.
createGrpcWebTransport() provides gRPC-web access.
Basic client init will be as follows:
import { createGrpcWebTransport } from "@connectrpc/connect-web";
import { createPromiseClient } from "@connectrpc/connect";
// import Client stub generated by `buf generate`
import { GeneratedService } from './generated_service_connect.js';
const transport = createGrpcWebTransport({
baseUrl: "https://example.com",
useBinaryFormat: true,
credentials: "include",
interceptors: [],
});
const client = createPromiseClient(GeneratedService, transport);
You can use createPromiseClient() both for Unary and Streaming methods.
Method call
buf generate creates each message type like described in
official example.
While you can create each object with new User(), Connect client accepts just plain objects:
const result = await client.createUser({
firstName: "Homer",
lastName: "Simpson",
active: true,
locations: ["Springfield"],
manager: {
firstName: "Montgomery",
lastName: "Burns",
},
});
This way looks straight forward in many usages.
- Prop names need to be camelCase, even proto is written in snake_case.
- Returning value is also just plain object, having camelCase props.
Interceptors just work
As
official doc describes, interceptors can be defined as plain function that receives request object. The request detail is node described in docs, you need to consult with its
implementation.
If you want to modify HTTP headers, you can do it through req.header as a
Fetch API Headers interface.
An interceptor can handle both unary and stream transport with internal conditional switch. Stream responses need to be handled with
generators.
At this time, unary/stream requests have the same behavior, as gRPC-web haven’t supported client-side stream for years.
BigInt conversion
ConnectRPC provides generally better gRPC-web client, but you need to pay extra attentions on int64 values.
Connect treats protobuf int64 as javascript BigInt having several limitations:
BigIntcannot be mixed with genericNumberBigIntcannot be serialized includingJSON.stringify()
For example, several data stores like Redux require serialization.
BigInt is not compatible with other types, and Connect doesn’t provide type conversion.
If you put generic Number into RPC request, clients throw just int32 to its service.
You may need to manually cast with Number() and BigInt() in many places.