gRPC between Java and ReactJS

Slimen Arnaout
Geek Culture
Published in
10 min readSep 6, 2021

RPC stands for Remote Procedure Call and gRPC is an RPC framework developed by Google in 2015.

Like REST or GraphQL, gRPC helps communicate between microservices. However it’s a bit different because it uses HTTP/2 instead of HTTP/1, and this is how gRPC gained its popularity because using HTTP/2 is faster and more secure than HTTP/1.

How to communicate between services? Protocol Buffers is the way to go

Protocol buffers are a mechanism for serializing structured data, it defines the shape of the data being sent or received from different services including properties and methods.

the shape of the data must be defined in a .proto file like so:

syntax=”proto3";package greet;message Greeting{   string first_name = 1;   string last_name = 2;}message GreetRequest{   Greeting greeting = 1;}message GreetResponse{   string result = 1;}service GreetService{    rpc Greet(GreetRequest) returns (GreetResponse){};}

This is an example of data that will be used to communicate between microservices. we have Greeting object that contains 2 attributes along with GreetRequest (will be sent as a request) and GreetResponse (will be the response from services) and the most important is the GreetService that contains the method that can be called from any service which is Greet that takes GreetRequest as a parameter and returns a GreetResponse.

This way you know exactly what to send and what kind of data you will receive.

But this is not all, in a real-world scenario an enterprise application can be composed of different microservices that are based on different programming languages (Java, Python, PHP, etc…) therefore we need to declare these data for each language.

now you must be thinking “am I going to write everything for each service?!!”

The answer is NO, for that we have a tool called protoc that can compile your code in many supported languages so no need to worry 😁.

Now, this is what you need to know to start with gRPC and if you want to dive deeper you can check the official website it is very comprehensive.

Prerequisites:

Now I will show how to start with gRPC using Java and ReactJS, but before that make sure to install these 3 things:

  1. protoc: which is the compiler, you can find the installation here https://grpc.io/docs/protoc-installation/, you must be able to run “protoc” from your command line.
  2. protoc-gen-grpc-java: which is a protoc plugin that will help us generate java code from proto files. (you can find it here https://repo1.maven.org/maven2/io/grpc/protoc-gen-grpc-java/1.40.1/)

NB: for MacOS it’s a bit tricky, you need to download protoc-gen-grpc-java-1.40.1-osx-x86_64.exe and since it’s a windows app you need to remove the .exe extension, move the file to /usr/local/bin and then run chmod +x /usr/local/bin/protoc-gen-grpc-java

3. protoc-gen-grpc-web: which is a protoc plugin to generate a TS code from proto files. (you can find it here https://github.com/grpc/grpc-web/releases an make sure to move the binary to /usr/local/bin like we did to the java plugin)

Hands-on:

Now we will create 3 folders:

  1. proto: where we will store the proto buff files that will be generated for both java and react.
  2. java: the java microservice
  3. react: the frontend of our small application

You should have something similar to this:

Create Proto file:

let’s now create our proto file called greet.proto with the following content:

syntax="proto3";// syntax of the proto file (current version is 3)
package proto.greet; // package where we will generate the file

option java_multiple_files = true; // use multiple file for each object instead of one big file

message Greeting{
string first_name = 1;
string last_name = 2;
}

message GreetRequest{
Greeting greeting = 1;
}

message GreetResponse{
string result = 1;
}

service GreetService{
rpc Greet(GreetRequest) returns (GreetResponse){};
}

here we will have 3 objects that can be transferred between our services which are Greeting, GreetRequest and GreetResponse, and we have our service GreetService that will tell us how to communicate using the method Greet that takes GreetResuqest as parameter and return GreetResponse.

Setting up the java service

Simply if you have IntellijIDEA you can start a new java project (I’m using Gradle instead of Maven so please follow me to have the same experience).

After the project is ready we need to add the gRPC dependencies to build.gradle file within the dependencies section like so:

dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
// these are the dependecies related to grpc
implementation 'io.grpc:grpc-netty-shaded:1.40.1'
implementation 'io.grpc:grpc-protobuf:1.40.1'
implementation 'io.grpc:grpc-stub:1.40.1'
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
}

NB: make sure to download them by clicking on the small button on the top right corner of IntellijIDEA.

Generate code for Java:

now to the awesome part! We want now to transfer the content of the proto file to java classes, and this can be done by running this command:

$ cd /path/to/proto/folder # make sure to enter the proto folder to run the command from there
$ protoc -I="./" --plugin=protoc-gen-grpc-java=/path/to/protoc-gen-grpc-java --grpc-java_out="./../java/src/main/java" --java_out="./../java/src/main/java" "./greet.proto"

let’s understand the command:

  1. protoc: is the compiler
  2. -I: a flag to determine the location of the proto files (in our case in the current location)
  3. — plugin: is the flag we used to determine to which language we want to convert our ptoto file
  4. — grpc-java_out && java_out: both need to generate object and service from the proto file so you need to specify the location where to geenrate those files
  5. “./greet.proto”: name of the file to generate

and BOOM 😎, you can see the generated files like so:

We’re not done yet don’t be happy 😊, now the protoc generated the base classes for our objects and service but now we need to implement our own logic to the greet method, what exactly we want to do when some service call the greet method is up to us to define it.

and to do that we will create a class called GreetServiceImpl that will extends from the class generated by protoc which is GreetServiceImplBase.

as you can see I have created a package called com.grpc.service and inside it I have created my GreetServiceImpl class that extends from GreetServiceGrpc.GreetServiceImplBase generated by protoc that contains the greet method where we will define our own logic, and this is how I did it:

package com.grpc.service;

import io.grpc.stub.StreamObserver;
import proto.greet.GreetRequest;
import proto.greet.GreetResponse;
import proto.greet.GreetServiceGrpc;
import proto.greet.Greeting;

public class GreetServiceImpl extends GreetServiceGrpc.GreetServiceImplBase {

@Override
public void greet(GreetRequest request, StreamObserver<GreetResponse> responseObserver) {
System.out.println("You are in the greet method or the greet service");

// we get the greeting object from the request (as defined in the proto file)
Greeting greeting = request.getGreeting();
String result = "Hello " + greeting.getFirstName() + greeting.getLastName();

// build our response where the type should be GreetResponse
GreetResponse response = GreetResponse.newBuilder()
.setResult(result)
.build();

responseObserver.onNext(response);// send the response
responseObserver.onCompleted();// complete the execution
}
}

Now let’s build the server, first create a new package called com.grpc.server and under it create the ServerImpl.java class and copy this content:

package com.grpc.server;

import com.grpc.service.GreetServiceImpl;
import io.grpc.Server;
import io.grpc.ServerBuilder;

public class ServerImpl {

public static void main(String[] args) throws Exception{
final int PORT = 9090;

// Create a new server to listen on port 9090
Server server = ServerBuilder.forPort(PORT)
.addService(new GreetServiceImpl())
.build();

// Start the server
server.start();

// Server threads are running in the background.
System.out.println("Server started...");
// Don't exit the main thread. Wait until server is terminated.
server.awaitTermination();
}
}

now you can run the server and you should see “Server started…” in the terminal. And by doing this our work is done for the java side, now let’s go to React shall we 😊.

Setting up the ReactJS service:

create a TypeScript based react application using the following command:

$ npx create-react-app react-grpc --template typescript

like the java dependencies, we have two dependencies to add to react which are grpc-web and google-protobuf, so make sure to install it:

$ cd react-grpc
$ npm i grpc-web google-protobuf

now let’s generate typescript code from our proto file like we did to java by running the following commands:

$ cd path/to/react-grpc # access the react project
$ mkdir src/output # create a folder called "output" where to store the generated code
$ cd path/to/proto/folder # access the proto files folder
$ protoc ./greet.proto --js_out=import_style=commonjs:./src/output --grpc-web_out=import_style=typescript,mode=grpcwebtext:./src/output # this will generate the typescript code

NB: if the protoc command didn’t work please verify the protoc-gen-grpc-web plugin you should set it up properly in order for this command to work.

now you should see something like this in your react project:

Awesome, as you can see from one single file we can generate different source codes for our services and make them all speak the same language :)

Communicate between React and Java:

now go to the App.tsx file, remove all the code in their and copy-paste this one:

import './App.css';
import { GreetServiceClient } from './output/GreetServiceClientPb';
import { Greeting, GreetRequest, GreetResponse } from './output/greet_pb';

function App() {

const grpcCall = () => {

// create our greeting object
let greeting = new Greeting();
greeting.setFirstName('Slimen')
greeting.setLastName('Arnaout')

// prepare the greet request
const request = new GreetRequest();
request.setGreeting(greeting);

// create gRPC client that will call ou java server
const client = new GreetServiceClient('http://localhost:9090')
.greet(request, {}, (err, response) => {
console.log({err, response});
});
}

return (
<div>
<button onClick={grpcCall}>Click</button>
</div>
);
}

export default App;

simply, what we did here we just created our Greeting and GreetRequest objects, after that we called the GreetServiceClient that allows us to call the greet method on the java side.

Under the hood the gRPC will call a specific URL that will communicate with java to get the response from it.

Now you can run the application by typing “npm start” in the console.

Unexpected behavior!!

If we click on the button to send a request to the java app we get this error in the console

and in the java side we get this message

Sep 06, 2021 1:09:17 PM io.grpc.netty.shaded.io.grpc.netty.NettyServerTransport notifyTerminated
INFO: Transport failed
io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception: Unexpected HTTP/1.x request: OPTIONS /proto.greet.GreetService/Greet

Now what’s happening here ?!!

As I mentioned at the beginning of this article gRPC uses HTTP/2 instead of HTTP/1 and what just happened is that our React app is trying to send a simple HTTP/1 request to our java service that’s why it is complaining about it.

// the react app is trying to access directly to the java service via the port 9090
const client = new GreetServiceClient('http://localhost:9090')
.greet(request, {}, (err, response: GreetResponse) => {
console.log({err, response});
});

as you can see in this snippet of code react is trying to call directly our java service via port 9090, and to solve this we need a proxy in the middle between the two services that is going to convert the HTTP/1 requests into HTTP/2, and for this we will use Envoy. You can read more about Envoy here https://www.envoyproxy.io/docs/envoy/latest/intro/what_is_envoy.

Fix the problem:

First we need to change the port in the react app to 8080 instead of 9090

const client = new GreetServiceClient('http://localhost:8080') <==
.greet(request, {}, (err, response: GreetResponse) => {
console.log({err, response});
});

Setting up Envoy:

Now we need to configure Envoy to listen to the port 8080 and convert each request to HTTP/2 before sending it to the java service via the port 9090, for that we need to create a YAML file in the react project called envoy.yaml and copy-paste this content in it:

admin:
access_log_path: /tmp/admin_access.log
address:
socket_address: { address: 0.0.0.0, port_value: 9901 }

static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 8080 } # the port to listen to
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route:
cluster: greeter_service # name of the service, you can see it in the bottom of thi file
max_stream_duration:
grpc_timeout_header_max: 0s
cors:
allow_origin_string_match:
- prefix: "*"
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
max_age: "1728000"
expose_headers: custom-header-1,grpc-status,grpc-message
http_filters:
- name: envoy.filters.http.grpc_web
- name: envoy.filters.http.cors
- name: envoy.filters.http.router
clusters:
- name: greeter_service # name of our java service
connect_timeout: 0.25s
type: logical_dns
http2_protocol_options: {}
lb_policy: round_robin
load_assignment:
cluster_name: greeter_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: host.docker.internal
port_value: 9090 # port of the java service

and as a final step we need to use envoy’s docker image, and to simplify things I’m going to use docker-compose, so this is my docker-compose.yaml file:

version: '3'
services:
envoy:
image: envoyproxy/envoy-dev:b145180d17cac80aa5f9a7801429d52017fea6d1
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml
ports:
- "8080:8080"
- "9901:9901"

NB: make sure to have the envoy.yaml and docker-compose.yaml in same location

And that’s it our gRPC project is now ready, and to run it make sure to:

  1. run the java backend
  2. run the react app via “npm start”
  3. run the Envoy proxy using “docker-compose up”

If everything is working fine you should see the response of the java service in the console like so:

Summary

as you can see, it’s a small project but it’s an easy way to understand how to start with gRPC and how to make it work with different services, the steps we did in this article are pretty much the same for each programming languages.

If you want to go deeper with gRPC I encourage you to read the official documentation.

I hope you enjoyed reading this and wish you a nice day 😊

--

--

Slimen Arnaout
Geek Culture

Senior Software Engineer @MaibornWolff, I help with software renovation and cloud migration, love soccer and video games .. and no, I don’t like cooking