Use gRPC with Node.js and Typescript
Tags: grpc, node, typescript, tutorial
Published: 2021-10-08 04:29:34

gRPC is a modern open-source high-performance Remote Procedure Call (RPC) framework that can run in any environment. And in this article, I am going to teach you how you can use gRPC to create high-performance RPC apps using node.js and typescript.

## What is gRPC?
gRPC is a technology developed at Google in 2015. It is an RPC framework that will help you create RPC applications in many of your favorite languages. If you don't know what RPC is don't worry I'm going to explain it soon. This technology is used by google itself too. It is used quite a lot with microservice structures. according to [Evaluating Performance of REST vs. gRPC](https://medium.com/@EmperorRXF/evaluating-performance-of-rest-vs-grpc-1b8bdf0b22da#:~:text=gRPC%20is%20roughly%207%20times,of%20HTTP%2F2%20by%20gRPC.) from [Ruwan Fernando](https://medium.com/@EmperorRXF) gRPC is roughly 7 times faster than REST when receiving data and roughly 10 times faster than REST when sending data in the case he tested.

![gRPC logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8susytd9w6lxe9sreqvd.jpg)

## What is RPC?
RPC is when a computer calls a procedure to execute in another address space. It is like calling another program to run action as it was ran on your computer and because of this, the request can be so much faster than REST.

Now lets go and create a simple application for sending hello messages.

## Setup Project.

1- Initialize your project:

```
mkdir grpc-starter
cd grpc-starter
npm init -y
```

2- Initialize typescript with your favorite config:
```
tsc init
```
I use the following as my typescript configuration in the `tsconfig.json` file. you can use whatever matches your need the best
```
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": [
      "es6"
    ],
    "allowJs": true,
    "outDir": "build",
    "rootDir": "src",
    "strict": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    "resolveJsonModule": true
  }
}
```

3- create the folder structure:

+ `/proto`: proto buffers folder(I will explain more later)
+ `/src`: the source directory
+ `/src/server`: server directory
+ `/src/client`: client directory
+ `/src/proto`: auto generated code from proto buffers

```
grpc-starter/
├── proto/
└── src/
    ├── client/
    ├── proto/
    └── server/
```

There are two ways to work with proto buffers and code generation in gRPC; dynamic or static. In static, we will generate types and code from our proto buffers but in dynamic we will not generate any typings from proto buffers and will use the code instead. dynamic can be a pretty good option if we were using JavaScript but since we need the typings to make our work easier while using TypeScript we will use the static way.

## Create Proto Buffers

Proto Buffers are a way to serialize data. You may be very familiar with some other serialization languages like JSON and XML. Proto Buffers are just like them and it is developed by Google and wildly used with gRPC. In this article I'm not going to talk more about them, that's for another article. 

First, we need to create the language enum. Well, you need to know a bit about folder structure in proto buffers we will create the language enum in `/proto/com/language/v1/language.proto` this is a package style folder structure that is necessary while using proto buffers with gRPC. 
```
// /proto/com/language/v1/language.proto
syntax = "proto3";

package com.language.v1;

message Language {
  enum Code {
    CODE_UNSPECIFIED = 0;
    CODE_EN = 1;
    CODE_FA = 2;
  }
}
```

Now we have to create our hello service in `/proto/services/hello/v1/hello_service.proto`.

```
// /proto/services/hello/v1/hello_service.proto
syntax = "proto3";

import "com/language/v1/language.proto";

package services.hello.v1;

service HelloService {
  rpc Greet(GreetRequest) returns (GreetResponse) {}
}

message GreetRequest {
  string name = 1;
  com.language.v1.Language.Code language_code = 2;
}

message GreetResponse {
  string greeting = 1;
  reserved "language_code";
  reserved 2;
}
```

## Buf

We will use a tool call Buf that will make code generation way easier for us. Check out the [installation page](https://docs.buf.build/introduction) to understand how you can install Buf.

Now we need to generate our buf config file at `/proto/buf.yaml`

```
# /proto/buf.yaml
version: v1beta1
build:
  roots:
    - .
lint:
  use:
    - DEFAULT
breaking:
  use:
    - WIRE_JSON
```

> The `v1` directory that we have in our folder structure is because of the linting setting that we are using you can default the linting setting and use a different folder structure if you wish. The linting structure has also affected some of my code that you can check in [Buf Docs](https://docs.buf.build/introduction).

Now you can run the commands below in `/proto` directory to check your code:

```
$ buf ls-files
com\language\v1\language.proto
services\hello\v1\hello_service.proto
```

You can check your code for linting errors too. And if your proto buffers don't have any problem the command will return empty:

```
$ buf lint
```

> If you have used the code provided by me and your buf version is `1.0.0-rc1` your lint command should return no error.

## Generating code

Well for code generation you can use `protoc` as it's the more popular tool but working with `protoc` is exhausting so we are going to use buf.

Now you need to generate the buf generation config at `/proto/buf.gen.yaml`:

```
# /proto/buf.gen.yaml
version: v1beta1
plugins:
  - name: js
    out: ../src/proto
    opt: import_style=commonjs,binary
  - name: grpc
    out: ../src/proto
    opt: grpc_js
    path: grpc_tools_node_protoc_plugin
  - name: ts
    out: ../src/proto
    opt: grpc_js
```

Now you have to install grpc-tools and grpc_tools_node_protoc_ts using `npm` or `yarn`. These two package will help us generate code for TypeScript using buf:

```
$ npm i -D grpc-tools grpc_tools_node_protoc_ts
or
$ yarn add -D grpc-tools grpc_tools_node_protoc_ts
```

Now you need to run the generate command inside `/proto` directory to generate code from proto buffers:

```
$ buf generate
```

## Implement the server

First thing we need to do is to add the gRPC package to create our server:

```
$ npm i @grpc/grpc-js
or
$ yarn add @grpc/grpc-js
```

Now create the `/src/server/index.ts` file and start the gRPC using the code below: 

```
import {
	Server,
	ServerCredentials,
} from '@grpc/grpc-js';
const server = new Server();

server.bindAsync('0.0.0.0:4000', ServerCredentials.createInsecure(), () => {
	server.start();

	console.log('server is running on 0.0.0.0:4000');
});
```

Using this code we can create a new server and bind it to `0.0.0.0:4000` which is like starting an express server at port `4000`.

Now we can take advantage of our statically generated code to create a typed Greet handler like below:

```
import {
	ServerUnaryCall,
	sendUnaryData,
	Server,
	ServerCredentials,
} from '@grpc/grpc-js';

import {Language} from '../proto/com/language/v1/language_pb';
import {
	GreetRequest,
	GreetResponse,
} from '../proto/services/hello/v1/hello_service_pb';

const greet = (
	call: ServerUnaryCall<GreetRequest, GreetResponse>,
	callback: sendUnaryData<GreetResponse>
) => {
	const response = new GreetResponse();

	switch (call.request.getLanguageCode()) {
		case Language.Code.CODE_FA:
			response.setGreeting(`سلام، ${call.request.getName()}`);
			break;
		case Language.Code.CODE_UNSPECIFIED:
		case Language.Code.CODE_EN:
		default:
			response.setGreeting(`Hello, ${call.request.getName()}`);
	}

	callback(null, response);
};

...
```

Now we have to add the service to server:

```
...

import {HelloServiceService} from '../proto/services/hello/v1/hello_service_grpc_pb';

...

server.addService(HelloServiceService, {greet});

...
```

At the end your server file should look like something like this:

```
import {
	ServerUnaryCall,
	sendUnaryData,
	Server,
	ServerCredentials,
} from '@grpc/grpc-js';

import {Language} from '../proto/com/language/v1/language_pb';
import {
	GreetRequest,
	GreetResponse,
} from '../proto/services/hello/v1/hello_service_pb';
import {HelloServiceService} from '../proto/services/hello/v1/hello_service_grpc_pb';

const greet = (
	call: ServerUnaryCall<GreetRequest, GreetResponse>,
	callback: sendUnaryData<GreetResponse>
) => {
	const response = new GreetResponse();

	switch (call.request.getLanguageCode()) {
		case Language.Code.CODE_FA:
			response.setGreeting(`سلام، ${call.request.getName()}`);
			break;
		case Language.Code.CODE_UNSPECIFIED:
		case Language.Code.CODE_EN:
		default:
			response.setGreeting(`Hello, ${call.request.getName()}`);
	}

	callback(null, response);
};

const server = new Server();

server.addService(HelloServiceService, {greet});

server.bindAsync('0.0.0.0:4000', ServerCredentials.createInsecure(), () => {
	server.start();

	console.log('server is running on 0.0.0.0:4000');
});
```

Now we can add `nodemon` to run our server and update it on change:

```
$ npm i nodemon
or
$ yarn add nodemon
```

And run the following command to start the server:

```
nodemon src/server/index.ts --watch /src/server
```

Now that we have our server ready let's go and create our client.

## Implement the client

Create the `/src/client/index.ts` file to start writing the client code.

In the client first we need to connect to our service client using the code below:

```
import {credentials} from '@grpc/grpc-js';

import {HelloServiceClient} from '../proto/services/hello/v1/hello_service_grpc_pb';

const client = new HelloServiceClient('localhost:4000', credentials.createInsecure());
```

Now we can create the request and populate it with our values like below:

```
...

import {Language} from '../proto/com/language/v1/language_pb';
import {GreetRequest} from '../proto/services/hello/v1/hello_service_pb';

...

const request = new GreetRequest();

request.setName('Aria');
request.setLanguageCode(Language.Code.CODE_EN);
```

At the end you can send the request and receive the response:

```
...

client.greet(request, (error, response) => {
	if (error) {
		console.error(error);

		process.exit(1);
	}

	console.info(response.getGreeting());
});
```

Your client file should look like this:
```
import {credentials} from '@grpc/grpc-js';

import {Language} from '../proto/com/language/v1/language_pb';
import {HelloServiceClient} from '../proto/services/hello/v1/hello_service_grpc_pb';
import {GreetRequest} from '../proto/services/hello/v1/hello_service_pb';

const client = new HelloServiceClient(
	'localhost:4000',
	credentials.createInsecure()
);

const request = new GreetRequest();

request.setName('Aria');
request.setLanguageCode(Language.Code.CODE_EN);

client.greet(request, (error, response) => {
	if (error) {
		console.error(error);

		process.exit(1);
	}

	console.info(response.getGreeting());
});
```

Run your client using the following command:

```
$ nodemon src/client/index.ts --watch src/client
```

## Final words

Huge shoutout to [Slavo Vojacek](https://slavovojacek.medium.com/) for his article on handling the proto buffers for typescript that has helped this article a lot.

You can check out the full repository at [my GitHub repo](https://github.com/Dev-Addict/grpc-node-ts)

While gRPC is amazing and super fast but it is not the best practice to use it for freelancing projects and small projects cause it will cost you a lot of time compared to REST but if you are building a dream and you want it to be the best you can have gRPC as an option and think if it is worth the cost.

### Resources

- [gRPC](https://grpc.io/)

- [gRPC - Wikipedia](https://en.wikipedia.org/wiki/GRPC)

- [Evaluating Performance of REST vs. gRPC | Medium](https://medium.com/@EmperorRXF/evaluating-performance-of-rest-vs-grpc-1b8bdf0b22da)

- [Remote procedure call - Wikipedia](https://en.wikipedia.org/wiki/Remote_procedure_call)

- [gRPC on Node.js with Buf and TypeScript — Part 1 | medium](https://slavovojacek.medium.com/grpc-on-node-js-with-buf-and-typescript-part-1-5aad61bab03b)

- [Introduction | Buf](https://docs.buf.build/introduction)

### Find Me

- [`@AriaAzadiPour`](https://twitter.com/AriaAzadiPour) on Twitter