Seeing both ends of a call makes every later topic concrete.
In Node.js the official implementation is @grpc/grpc-js (pure JavaScript). There are two ways to get stubs:
- Dynamic loading with
@grpc/proto-loader: parse the.protoat startup and build client classes and service definitions at runtime. Quick to start, no build step. - Static code generation with
protocor Buf plus a plugin (e.g.ts-proto) for typed TypeScript code.
With dynamic loading, grpc.loadPackageDefinition() returns an object mirroring your packages, so hello.v1.Greeter is a client constructor and hello.v1.Greeter.service the service definition.
Server side, you addService(definition, handlers). A unary handler receives call (with call.request, call.metadata) and a callback(err, response). Bind to a port with bindAsync() and credentials; createInsecure() is for local development only.
Client side, construct the stub with an address and credentials, then call methods with (request, callback). Clients are cheap to call but expensive to create: create one per target at startup and reuse it, because it holds the HTTP/2 connection.
Proto-loader options matter: keepCase: true keeps snake_case field names (otherwise they become camelCase), defaults: true fills unset fields with defaults, and longs: String avoids precision loss for 64-bit integers.
Example
// hello.proto: service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); }
// message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
const def = protoLoader.loadSync("hello.proto", { keepCase: true, defaults: true });
const { hello } = grpc.loadPackageDefinition(def);
const server = new grpc.Server();
server.addService(hello.v1.Greeter.service, {
SayHello: (call, callback) => callback(null, { message: `Hello, ${call.request.name}` }),
});
server.bindAsync("127.0.0.1:50051", grpc.ServerCredentials.createInsecure(), (err) => {
if (err) throw err;
const client = new hello.v1.Greeter("127.0.0.1:50051", grpc.credentials.createInsecure());
client.SayHello({ name: "Ann" }, (err, reply) => {
console.log(reply.message); // Hello, Ann
client.close();
server.forceShutdown();
});
});Remember
- @grpc/grpc-js is the Node implementation; proto-loader loads .proto at runtime
- Unary handlers:
(call, callback); clients:stub.Method(req, cb) - Create one client per target and reuse it
- Use
longs: Stringto keep int64 precision
Watch out for
- Creating a new client per request, paying a new connection and TLS handshake each time.