Using gRPC with NestJS and Angular

Kim T
Creative Technology Concepts & Code
7 min readMay 20, 2020

--

Angular, gRPC and NestJS

Background

If you haven’t already heard, gRPC is gaining a lot of traction in the development community at the moment. gRPC is an open-source remote procedure call standard which supports authentication and streaming of data between servers and clients. It originated at Google from their internal tool Stubby, which is used to power many Google services today.

If you are interested to learn more about gRPC, Microsoft has a good comparison between gRPC and traditional REST APIs, and there are some great posts showing benchmarks between gRPC traditional REST APIs. In some cases gRPC reduces processing time by 50%. So in short, it’s awesome and you should consider it for high-end performance applications.

It’s well documented how to implement gRPC for C-lang, Java, and other server-side languages since it’s most commonly used for high-performance micro-service architectures. There are already some great tutorials for using gRPC with NodeJS:

But what about using gRPC with TypeScript? with NestJS and Angular? Is it even possible? Here how I achieved it.

NestJS is a Backend server-side framework

Using gRPC with NestJS

First off, we want to install NestJS and then create a new project using the command line tool:

npm i -g @nestjs/cli
nest new nest-grpc
cd nest-grpc
npm run start

Then install the gRPC proto-loader dependencies and NestJS microservices:

npm i --save grpc @grpc/proto-loader @nestjs/microservices

I’m not going to show every single step of creating the example endpoints, as the official guide already shows this, and is kept up-to-date with NestJS releases. Follow the steps from the guide here:
https://docs.nestjs.com/microservices/grpc

If you want to skip these steps, you can clone the official NestJS gRPC example from Github directly:
https://github.com/nestjs/nest/tree/master/sample/04-grpc

Once completed, you should have a gRPC service running using NestJS.

NestJS gRPC service is running!

Testing your gRPC endpoints

Normal browser tools don’t natively support gRPC calls. You can explore and test your endpoints using grpcc:

npm install -g grpcc

Connect to your gRPC server on the correct port. Note: NestJS defaults to port 5000, but depending on your configuration it could be different:

grpcc -i --proto ./src/hero/hero.proto --address localhost:5000

Then call your endpoints using:

// call methods
client.getHeroes({}, printReply)
client.getHeroById({ id: 1 }, printReply)
// call streaming methods
var call = client.getHeroesStream().on('data', streamReply).on('status', streamReply); call.write({});
var call = client.getHeroByIdStream().on('data', streamReply).on('status', streamReply); call.write({ id: 1 });
Left: Exploring our gRPC endpoints, Right: calling client.getHeroes()

One important thing to note is that data has to be returned as an object. So you’ll noticed the array of heroes is nested inside an object called “heroes”.

The Github version of the NestJS server also exposes a REST API. You can use compare the two approaches REST vs gRPC, by accessing it in the browser:

http://localhost:3001/hero
http://localhost:3001/hero/1
NestJS can also a serve REST API with the same data!
Angular is a Frontend client-side framework

Using gRPC with Angular

Now our backend server is serving gRPC endpoints, how can we access them via the frontend? So it turns out that gRPC is not natively supported in the browser the explanation is:

For gRPC-Web to work, we need a lot of the underlying transport to be exposed to us but that’s not the case currently cross browsers. We cannot leverage the full http2 protocol given the current set of browser APIs. gRPC relies on trailing headers but browsers don’t expose those.

Therefore we need two additional steps before we can use gRPC in the browser:

  1. Generate client-side .proto stubs
  2. Use a proxy to support requests

Lets start off by creating a new Angular project:

npm install -g @angular/cli
ng new angular-grpc
cd angular-grpc
npm start

We need some additional tools to generate and load client-side .proto stubs for Angular. Install using the command:

npm install @improbable-eng/grpc-web google-protobuf grpc protoc ts-protoc-gen

Next we need to add a compile script to our Angular package.json to convert the .proto files using our tools:

{
"name": "angular-grpc",
"version": "0.0.2",
"description": "Angular gRPC example",
"scripts": {
"ng": "ng",
"start": "ng serve --host 0.0.0.0",
"build": "ng build",
"compile": "./node_modules/protoc/protoc/bin/protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --js_out=import_style=commonjs,binary:src/app/proto --ts_out=service=true:src/app/proto -I ../backend/src/ ../backend/src/**/*.proto",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
}
}

Every time your .proto files change, you need to regenerate the client-side stubs using this shortcut compile command:

npm run compile

After running the command, you should see the JavaScript and TypeScript definitions for your .proto model:

Auto-generated client-side services

One benefit of this, is that all our Types are created for us. No more manual updating of data types and services.

Our types are automatically generated from the .proto model definitions!

To use them, simply import the service to your Controller or Service and call the appropriate method:

import { Injectable } from '@angular/core';
import { HeroServiceClient } from './proto/hero/hero_pb_service';
import { HeroById, Hero, HeroList } from './proto/hero/hero_pb';
@Injectable({
providedIn: 'root'
})
export class ApiService {
client: HeroServiceClient;
constructor() {
this.client = new HeroServiceClient('http://localhost:3001');
}
getHero(path, val): Promise <Hero> {
return new Promise((resolve, reject) => {
const req = new HeroById();
req.setId(val);
this.client.getHeroById(req, null, (err, response: Hero) => {
if (err) {
return reject(err);
}
resolve(response.toObject());
});
});
}
}

Adding a proxy for gRPC requests

When you run the Angular app, you’ll notice the Angular Service cannot connect to the gRPC endpoint, even though the address is correct:

Why are we seeing a POST request for GRPC?

Going back to earlier, you’ll remember that browsers don’t have full support for gRPC yet, so the gRPC-web library is sending a POST request instead:

The @improbable-eng/grpc-web client uses multiple techniques to efficiently invoke gRPC services. Most modern browsers support the Fetch API, which allows for efficient reading of partial, binary responses. For older browsers, it automatically falls back to XMLHttpRequest.

Until there is better cross-browser support for gRPC, we can use Envoy Proxy to pass through requests. We start by adding an envoy.yaml to configure our proxy settings. Note: you may need to change the hosts target port_value from 5000 depending on your NestJS server settings:

admin:
access_log_path: /tmp/admin_access.log
address:
socket_address: { address: 0.0.0.0, port_value: 8081 }
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route:
cluster: echo_service
max_grpc_timeout: 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.grpc_web
- name: envoy.cors
- name: envoy.router
clusters:
- name: echo_service
connect_timeout: 0.25s
type: logical_dns
http2_protocol_options: {}
lb_policy: round_robin
hosts: [{ socket_address: { address: localhost, port_value: 5000 }}]

We can now run Envoy Proxy locally using a Docker command:

docker run -d -p 8080:8080 -p 8081:8081 -t envoyproxy/envoy-alpine:v1.14.1 -v ./envoy.yaml:/etc/envoy/envoy.yaml

If successful you should see the proxy running in your terminal:

Envoy Proxy running locally

Now we can point our Angular client-side requests to Envoy Proxy at port 8080, and it will forward them onto our NestJS server at port 5000!

this.client = new HeroServiceClient('http://localhost:8080');

Running Angular again, you should see requests get sent to the Envoy Proxy on port 8080, and returning with gRPC data!

Our response was successful!

Summary

There are some pros and cons to implementing gRPC for client-side applications. The main negative points are:

  • Use of a proxy is not ideal, and adds overhead & complexity.
  • Requires addition gRPC-web libraries for compatibility.
  • If you have multiple auto-generated client-side services, they contain repeated code, adding bloat to your application.
  • Takes time for a team to skill-up and support a new data format.

The positives of gRPC for client-side applications:

  • Proto buffers allow a single-source of truth for models, which are language-agnostic.
  • We can auto-generate our Types and Services, saving time from keeping everything up-to-date manually.
  • Even the fallback fetch requests allow more efficient reading of partial, binary responses vs a standard REST API, and using gRPC-web is easier than implementing your own fetch binary requests manually.
  • In the future as browser support improves, the amount of polyfill and generated code should be reduced.

Hope that helps you get started with gRPC and TypeScript!

You can check out my full demo with source code here:
https://github.com/kmturley/angular-nest-grpc

--

--

Kim T
Creative Technology Concepts & Code

Creative Technologist, coder, music producer, and bike fanatic. I find creative uses for technology.