Full featured integration library for React and gRPC-Web. Core functions include: packaging the generated proto messages and client stubs, a unified API of gRPC call methods that support Google's and Improbable's gRPC-web specs for unary, client streaming, server streaming and bi-directional streaming.
npm install --save reactrpc
Create proto files as the schema for your Server and Client Stubs. It should define the gRPC call methods needed to communicate between the Server and Browser. These files will be used to give your components superpowers -- Remote Procedure Call (RPC) methods.
helloworld.proto
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
book_service.proto
syntax = "proto3";
package examplecom.library;
message Book {
int64 isbn = 1;
string title = 2;
string author = 3;
}
message GetBookRequest {
int64 isbn = 1;
}
message QueryBooksRequest {
string author_prefix = 1;
}
service BookService {
rpc GetBook(GetBookRequest) returns (Book) {}
rpc QueryBooks(QueryBooksRequest) returns (stream Book) {}
}
In order to pass superpowers to our Browser, we first need to package our .proto file.
To generate the protobuf messages and client service stub class from your
.proto
definitions, we need the protoc
binary and the
protoc-gen-grpc-web
plugin.
You can download the protoc-gen-grpc-web
protoc plugin from Google's
release page:
If you don't already have protoc
installed, you will have to download it
first from here.
Make sure they are both executable and are discoverable from your PATH.
For example, in MacOS, you can do:
$ sudo mv ~/Downloads/protoc-gen-grpc-web-1.0.7-darwin-x86_64 \
/usr/local/bin/protoc-gen-grpc-web
$ chmod +x /usr/local/bin/protoc-gen-grpc-web
When you have both protoc
and protoc-gen-grpc-web
installed, you can now
run this command:
$ protoc -I=. helloworld.proto \
--js_out=import_style=commonjs:. \
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:.
After the command runs successfully on your [name of proto].proto
you should see two generated files [name of proto]_pb.js
which contains the messages and [name of proto]_grpc_web_pb.js
that contains the services:
For instance the helloworld.proto
file will generate to:
- messages :
helloworld_pb.js
- services :
helloworld_grpc_web_pb.js
For the latest stable version of the ts-protoc-gen plugin:
npm install ts-protoc-gen
Download or install protoc (the protocol buffer compiler) for your platform from the github releases page or via a package manager (ie: brew, apt).
Download protoc from here
When you have both protoc
and ts-protoc-gen
installed, you can now run this command:
--plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts \
-I ./proto \
--js_out=import_style=commonjs,binary:./ts/_proto \
--ts_out=service=true:./ts/_proto \
./proto/examplecom/library/book_service.proto
After the command runs successfully on your [insert_name].proto
you should see two generated files [insert_name]_pb.js
which contains the messages and [insert_name]_pb_service.js
that contains the services:
For instance for the helloworld.proto you should see:
- messages :
book_service_pb.js
- services :
book_service_pb_service.js
In order for gRPC-web to communicate with other gRPC servers, it requires a proxy server as a translation layer to convert between gRPC-web protobuffers and gRPC protobuffers. Links to examples on how to set those up can be found here (Envoy proxy) and here (Improbable's proxy)*
*Note: To enable bidirectional/client-side streaming you must use Improbable's spec and its proxy with websockets enabled
Require in the reactRPC library and protobuf files in your React JSX file. Run the build method with the following params: the message, the services and the URL to the proxy server endpoint.
const { googleRPC } = require("reactRPC")
const messages = require("helloworld_pb.js")
const services = require("helloworld_grpc_web_pb.js")
const URL = "http://" + window.location.hostname + ":8080"
googleRPC.build(messages, services, URL)
Export the googleRPC component by passing it as an argument into the reactRPC wrapper as follows:
export default googleRPC.wrapper(<your component>);
const { improbRPC } = require("reactRPC")
const messages = require("book_service_pb.js")
const services = require("book_service_pb_service.js")
const URL = "http://" + window.location.hostname + ":8080"
improbRPC.build(messages, services, URL)
Export the improbRPC component by passing it as an argument into the improbRPC wrapper as follows:
export default improbRPC.wrapper(<your component>);
We define a request message by creating an object with the keys as the message field along with a msgType
property specifying a message that we set in the proto file. Here is an example of a HelloRequest
message in the helloworld.proto
file :
const message = { name: "John", lastName: "Doe", msgType: "HelloRequest" }
We define a function by listing its service and procedure calls on this.props
. We then pass in the message we defined above, and an object with any metadata data required (learn more about metadata here). For unary calls a third parameter of a callback is required while streaming calls have built in event listeners.
// unary call:
this.props.Greeter.sayHello(
message,
{},
(err, response) => {
console.log(response)
}
);
// streaming call
const stream = this.props.Greeter.sayRepeatHello(
message,
{}
);
stream.onMessage(res => {
console.log(res.getMessage());
});
ReactRPC library supports unary, client-side, server-side and bi-directional streaming.
In the proto file, messages can be nested in other messages. In this example, the FullName message is used in the TestNested message:
message FullName{
string name = 1;
string lastName = 2;
}
message TestNested{
FullName myName = 1;
}
For flexibility ReactRPC models both Google and Improbable's eventlisteners:
Google's Implementation
const stream = this.props.Greeter.sayRepeatHello(
message,
{}
);
stream.on("data", res => {
console.log(res.getMessage());
});
stream.on("status", res => {
console.log(res.getMessage());
});
stream.on("end", res => {
console.log(res.getMessage());
});
Improbable's Implementation
const stream = this.props.Greeter.sayRepeatHello(
message,
{}
);
stream.onMessage(res => {
console.log(res.getMessage());
});
stream.onHeaders(res => {
console.log(res.getMessage());
});
stream.onEnd(res => {
console.log(res.getMessage());
});
For
improbable's
bidirectional streaming there is client.send that opens the stream and client.finishSend to close the stream as follows:
const stream = this.props.Greeter.sayRepeatHello(
message,
{}
);
stream.send({ name: "John", lastName: "Doe", msgType: "SayHelloRequest" });
});
stream.finishSend({ name: "John", lastName: "Doe", msgType: "SayHelloRequest" });
});