The @loopback/grpc
component enables LoopBack 4 as a gRPC Server. Also it provides with a gRPC decorator to define your RPC Method implementations from your Application Controllers.
- Handles unary, client streaming, server streaming and bidirectional streaming calls
- Provides options for TLS and mTLS
Install the @loopback/grpc
component in your LoopBack 4 Application.
$ npm install --save @loopback/grpc
import {Application} from '@loopback/core';
import {GrpcComponent, GrpcComponentConfig} from '@loopback/grpc';
import {GreeterController} from './controllers/greeter.controller';
// Grpc Configurations are optional.
const config: GrpcComponentConfig = {
/* Optional Configs */
};
// Pass the optional configurations
const app = new Application({
grpc: config,
});
// Add Grpc as Component
app.component(GrpcComponent);
// Bind GreeterController to the LoopBack Application
// only if your Boot Mixins do not load this directory
// see https://loopback.io/doc/en/lb4/Booting-an-Application.html
// app.controller(GreeterController);
// Start App
app.start();
The @loopback/grpc
extension provides you with auto-generated interfaces and configurations for strict development.
The extension will automatically look for proto files within your project structure, creating the corresponding typescript interfaces.
Example:
- app
| - protos
| | - greeter.proto
| - protos-ts
| - controllers
| | - greeter.controller.ts
Once you start your app for first time it will automatically create your typescript interfaces from the greeter.proto
file.
- app
| - protos
| | - greeter.proto
| - protos-ts
| | - greeter.ts <--- Auto-generated
| - controllers
| | - greeter.controller.ts
Once your interfaces and configurations are created, you can start building your controller logic.
NB: you can also manually generate your interfaces from _.proto files by creating a .js
file at the root of your project like below. A good practice would be to add the directory containing your generated .ts
files to .gitignore
and to add node generate.js
to the command npm run build
.
const GrpcGenerator = require('@loopback/grpc').GrpcGenerator;
const path = require('path');
const fs = require('fs');
function formatWithColor(message, color) {
return `\x1b[${color}m${message}\x1b[0m`;
}
const protoTsPath = path.join(process.cwd(), 'src', 'protos-ts');
try {
fs.statSync(protoTsPath);
} catch (e) {
fs.mkdirSync(protoTsPath);
}
const generator = new GrpcGenerator({
protoPattern: '**/protos/**/*.proto',
// additionalArgs: '--experimental_allow_proto3_optional',
tsOutputPath: protoTsPath,
protoPath: path.join(process.cwd(), 'src', 'protos'),
generate: true,
load: false,
});
console.log(formatWithColor('Generating proto.ts files from *.proto...', 33));
generator.execute();
console.log(formatWithColor('All proto.ts files have been generated', 32));
The @loopback/grpc
component provides you with a handy decorator to implement gRPC Methods within your LoopBack controllers. The decorator will automatically map the correct calls from the file descriptor, the method name and the controller name. If you want an other suffix than (Ctrl|Controller)
, you can use the argument controllerNameRegex
.
app/controllers/greeter.controller.ts
import {
sendUnaryData,
ServerDuplexStream,
ServerReadableStream,
ServerUnaryCall,
ServerWritableStream,
} from '@grpc/grpc-js';
import {
getSpecsFromMethodDefinitionAndProtoMetadata,
grpc,
} from '../../../decorators/grpc.decorator';
import BaseController from '../../../grpc.controller';
import {
GreeterServer,
GreeterService,
protoMetadata,
TestRequest,
TestResponse,
} from './greeter';
export default class GreeterController extends BaseController
implements GreeterServer {
// Tell LoopBack that this is a Service RPC implementation
@grpc(
getSpecsFromMethodDefinitionAndProtoMetadata(
GreeterService.unaryTest,
protoMetadata.fileDescriptor,
),
)
async unaryTest(
call: ServerUnaryCall<TestRequest, TestResponse>,
callback: sendUnaryData<TestResponse>,
): Promise<void> {
callback(null, {
message: 'Hello ' + call.request.name,
});
}
@grpc(
getSpecsFromMethodDefinitionAndProtoMetadata(
GreeterService.clientStreamTest,
protoMetadata.fileDescriptor,
),
)
async clientStreamTest(
call: ServerReadableStream<TestRequest, TestResponse>,
callback: sendUnaryData<TestResponse>,
): Promise<void> {
const names = await new Promise<string[]>((resolve, reject) => {
const names: string[] = [];
call.on('data', (chunk: TestRequest) => names.push(chunk.name));
call.on('error', (err) => reject(err));
call.on('end', () => resolve(names));
});
callback(null, {
message: names.join(' '),
});
}
@grpc(
getSpecsFromMethodDefinitionAndProtoMetadata(
GreeterService.serverStreamTest,
protoMetadata.fileDescriptor,
),
)
async serverStreamTest(
call: ServerWritableStream<TestRequest, TestResponse>,
): Promise<void> {
const req = call.request.name.split(' ');
const sleep = (seconds = 0) =>
new Promise((resolve) => setTimeout(resolve, seconds));
const writeAfterSleep = (payload: TestResponse, seconds = 0) =>
(async () => {
await sleep(seconds);
call.write(payload);
})();
const promises: Promise<void>[] = [];
promises.push(
writeAfterSleep({
message: req[0],
}),
);
for (let i = 1; i < req.length; i++) {
promises.push(
writeAfterSleep(
{
message: req[i],
},
i * 200,
),
);
if (i === req.length - 1) {
promises.push(
(async () => {
await sleep((i + 1) * 200);
call.end();
})(),
);
}
}
await Promise.all(promises);
}
@grpc(
getSpecsFromMethodDefinitionAndProtoMetadata(
GreeterService.bidiStreamTest,
protoMetadata.fileDescriptor,
),
)
async bidiStreamTest(
call: ServerDuplexStream<TestRequest, TestResponse>,
): Promise<void> {
await new Promise<string[]>((resolve, reject) => {
call.on('data', (chunk: TestRequest) =>
call.write({
message: `Got ${chunk.name} !`,
}),
);
call.on('error', (err) => reject(err));
call.on('end', () => call.end());
});
}
}
app/protos/greeter.proto
syntax = "proto3";
package greeterpackage;
service Greeter {
// Sends a greeting
rpc UnaryTest (TestRequest) returns (TestResponse) {}
rpc ClientStreamTest (stream TestRequest) returns (TestResponse) {}
rpc ServerStreamTest (TestRequest) returns (stream TestResponse) {}
rpc BidiStreamTest (stream TestRequest) returns (stream TestResponse) {}
}
// The request message containing the user's name.
message TestRequest {
string name = 1;
}
// The response message containing the greetings
message TestResponse {
string message = 1;
}
import {GrpcComponentConfig} from '@loopback/grpc';
const grpcConfig: GrpcComponentConfig = {
server: {
tls: {
rootCertPath: 'path/to/root/cert',
keyCertPairPaths: [
{
privateKeyPath: 'path/to/private/key/for/server',
certChainPath: 'path/to/cert/chain/for/server',
},
],
},
// set it to false/undefined to disable client certificate verification
checkClientCertificate: true,
},
};
Tests can be a good starting point for your client side integration.
Get started by either downloading this project or cloning it as follows:
$ git clone https://github.com/strongloop/loopback4-extension-grpc.git
$ cd loopback4-extension-grpc && npm install
run npm test
from the root folder.
- Maybe watch for proto changes.
See all contributors.
MIT