@grpc/grpc-js support

April 23, 2020 ยท View on GitHub

Why

@grpc/grpc-js is a great project. it's pure js implementation, this makes it's easy to build docker images & cooperation with electron, etc.

Recently grpc-js has published it's 1.0.1 version, means it's no longer beta. So it's a good time to support it.

Some information

Migrating from grpc to @grpc/grpc-js

Code generation

Want to use grpc-js, grpc-tools version 1.8.1 is REQUIRED.

Change your bash script from:

grpc_tools_node_protoc \
--js_out=import_style=commonjs,binary:./src/grpc/proto \
--grpc_out=./src/grpc/proto \
--plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` \
-I ./proto \
proto/*.proto

to:

grpc_tools_node_protoc \
--js_out=import_style=commonjs,binary:./src/grpcjs/proto \
--grpc_out=generate_package_definition:./src/grpcjs/proto \
-I ./proto \
proto/*.proto

--plugin is no longer necessary and add generate_package_definition in --grpc-out.

There are two generated files:

  • *_pb.js: Message codes have NO changes
  • *_grpc_pb.js: Service codes have some changes
- var grpc = require('grpc');

- var BookServiceService = exports.BookServiceService = {
+ var BookServiceService = exports['com.book.BookService'] = {

- exports.BookServiceClient = grpc.makeGenericClientConstructor(BookServiceService);
  • grpc is no longer necessary
  • exported service object has new name, a package name like: com.book.BookService
  • client object is no longer generated, users have to make it by self

Typings

grpc has it's official typings: index.d.ts. Also grpc-js has an official typings itself (it's not included in github's online source codes), it's in the dir node_modules/@grpc/grpc-js/build/src/index.d.ts, and online source codes may give some hint: index.ts.

There are several differences between them:

All the calls in grpc-js has both request & response definition: grpc.ServerUnaryCall<GetBookRequest, Book> while grpc only needs the request part: grpc.ServerUnaryCall<GetBookRequest>. This is the most big change.

And in version 1.0.1 grpc-js has two useful typings not exported in typings index, you need to manually import them:

import {sendUnaryData} from "@grpc/grpc-js/build/src/server-call";
import {handleClientStreamingCall} from "@grpc/grpc-js/build/src/server-call";

I guess it shall be a bug, maybe would be fixed later.

Usage

See full examples here:

Most changes:

server.ts

add services part:

// grpc
import { BookServiceService, ... } from "./proto/book_grpc_pb";
server.addService(BookServiceService, new ServerImpl());

// =>

// grpc-js
import * as bookGrpcPb from "./proto/book_grpc_pb";
// @ts-ignore
server.addService(bookGrpcPb["com.book.BookService"], new ServerImpl());

Since service object in js file is now exported with name like: "com.book.BookService", it's no longer possible to be imported with sentence: import { YourService } from "your_grpc_pb".

And // @ts-ignore is required, without this there would be compiling error: Index signature is missing in type 'ServerImpl'.. Some information could be found here: Index signature is missing in type (only on interfaces, not on type alias) #15300. Though it may be possible to be fixed by some hack, I just use comments to ignore it, since the implementation is correct.

start server part, changed to async:

// grpc
server.bind("127.0.0.1:50051", grpc.ServerCredentials.createInsecure());
server.start();

// =>

// grpc-js
server.bindAsync("127.0.0.1:50051", grpc.ServerCredentials.createInsecure(), (err, port) => {
    if (err) {
        throw err;
    }
    server.start();
});

client.ts

client object changes:

// grpc
import { BookServiceClient } from "./proto/book_grpc_pb";
const client = new BookServiceClient("127.0.0.1:50051", grpc.credentials.createInsecure());

// =>

// grpc-js
import * as bookGrpcPb from "./proto/book_grpc_pb";
const BookServiceClient = grpc.makeClientConstructor(bookGrpcPb["com.book.BookService"], "BookService");
const client = new BookServiceClient("127.0.0.1:50051", grpc.credentials.createInsecure());

Since client object is no longer created by grpc-tools code generation, it have to be generated by user codes.

What grpc_tools_node_protoc_ts changed

d.ts generating bash script change, from:

grpc_tools_node_protoc \
--plugin=protoc-gen-ts=../bin/protoc-gen-ts \
--ts_out=./src/grpc/proto \
-I ./proto \
proto/*.proto

to:

grpc_tools_node_protoc \
--plugin=protoc-gen-ts=../bin/protoc-gen-ts \
--ts_out=generate_package_definition:./src/grpcjs/proto \
-I ./proto \
proto/*.proto

Add generate_package_definition in --ts-out, be consistent with grpc-js.

There is no changes in *_pb.d.ts, and some changes in *_grpc_pb.d.ts:

- import * as grpc from "grpc";

+ import * as grpc from "@grpc/grpc-js";
+ import {handleClientStreamingCall} from "@grpc/grpc-js/build/src/server-call";

export interface IBookServiceServer {
    getBook: grpc.handleUnaryCall<book_pb.GetBookRequest, book_pb.Book>;
    getBooksViaAuthor: grpc.handleServerStreamingCall<book_pb.GetBookViaAuthor, book_pb.Book>;
-    getGreatestBook: grpc.handleClientStreamingCall<book_pb.GetBookRequest, book_pb.Book>;
+    getGreatestBook: handleClientStreamingCall<book_pb.GetBookRequest, book_pb.Book>;
    getBooks: grpc.handleBidiStreamingCall<book_pb.GetBookRequest, book_pb.Book>;
}

That's all.

If you are still using grpc, just do nothing, new version would not affect existing users.