Skip to main content
Version: v2

gRPC

gRPC is a Remote Procedure Call (RPC) framework that can run in any environment. It can connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

This page shows how to use gRPC in Foal. It is based on the official gRPC tutorial.

Installation & Configuration

First you need to install some additional dependencies.

npm install @grpc/grpc-js @grpc/proto-loader
npm install cpx2 --save-dev

Then update your package.json so that your build scripts will correctly copy your .proto files into the build/ directory.

{
"build": "foal rmdir build && cpx \"src/**/*.proto\" build && tsc -p tsconfig.app.json",
"develop": "npm run build && concurrently \"cpx \\\"src/**/*.proto\\\" build -w\" \"tsc -p tsconfig.app.json -w\" \"supervisor -w ./build,./config -e js,json,yml,proto --no-restart-on error ./build/index.js\"",
"build:test": "foal rmdir build && cpx \"src/**/*.proto\" build && tsc -p tsconfig.test.json",
"test": "npm run build:test && concurrently \"cpx \\\"src/**/*.proto\\\" build -w\" \"tsc -p tsconfig.test.json -w\" \"mocha --file ./build/test.js -w --watch-files build \\\"./build/**/*.spec.js\\\"\"",
"build:e2e": "foal rmdir build && cpx \"src/**/*.proto\" build && tsc -p tsconfig.e2e.json",
"e2e": "npm run build:e2e && concurrently \"cpx \\\"src/**/*.proto\\\" build -w\" \"tsc -p tsconfig.e2e.json -w\" \"mocha --file ./build/e2e.js -w --watch-files build \\\"./build/e2e/**/*.js\\\"\"",
...
}

The gRPC Service

Create your spec.proto file and save it to src/app.

syntax = "proto3";

package helloworld;

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}

Add the Greeter service.

services/greeter.service.ts

export class Greeter {

sayHello(call, callback) {
callback(null, {message: 'Hello ' + call.request.name});
}

}

The next step is to create a service that will instantiate the gRPC server.

services/grpc.service.ts

// std
import { join } from 'path';

// 3p
import { dependency } from '@foal/core';
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';

// App
import { Greeter } from './greeter.service';

export class Grpc {
@dependency
greeter: Greeter;

boot(): Promise<void> {
const PROTO_PATH = join(__dirname, '../spec.proto');
const packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
}
);

const helloProto: any = grpc.loadPackageDefinition(packageDefinition).helloworld;
const server = new grpc.Server();
server.addService(helloProto.Greeter.service, this.greeter as any);
// OR
// server.addService(helloProto.Greeter.service, {
// sayHello: this.greeter.sayHello.bind(this.greeter)
// } as any);

return new Promise((resolve, reject) => {
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), error => {
if (error) {
return reject(error);
}
server.start();
return resolve();
});
})
}

}

Finally, inject the service in the application.

export class AppController {
@dependency
grpc: Grpc;

// ...
}

Using Promises

Using callbacks in the grpc services can be quite tedious. To convert methods into functions that use promises, you can use the decorator below.

import { callbackify } from 'util';

function async (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.value = callbackify(descriptor.value);
};

export class Greeter {

@async
async sayHello(call) {
return { message: 'Hello ' + call.request.name };
}

}

Limitations

The implementation above do not use Foal regular controllers. This has two consequences:

  • hooks cannot be used,
  • and error-handling is entirely managed by the gRPC server. The AppController.handleError cannot be used.