Skip to main content
Version: 2.5.0 (terbaru)

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-loadernpm 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 greetingsmessage 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

// stdimport { join } from 'path';
// 3pimport { dependency } from '@foal/core';import * as grpc from '@grpc/grpc-js';import * as protoLoader  from '@grpc/proto-loader';
// Appimport { 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.