TypeScript brings the myriad benefits of static typing to JavaScript. At Blokur, it allows us to catch errors early, write code more quickly and refactor confidently.

We also make extensive use of gRPC for service-to-service communication. Protocol buffer service definitions make it dead easy for developers to know how to communicate with a given service. With some careful setup, interfacing with a remote service can feel as simple as calling a local function.

A fully-typed gRPC client provides a better development experience

It can, however, be a little fiddly to properly set up your TypeScript gRPC server/client implementation to properly use type definitions generated from your .proto files.

Without further ado, here’s how to achieve the perfect TypeScript + gRPC setup!

TL;DR: Go ahead and browse the source code of the demo project!

Let’s talk about Songs

Since Blokur is a music company, our demo application is going to be a CLI tool that allows you to discuss your favourite songs with your friends.

First, we’ll create our service definition in proto/songs.proto .

We will implement each of the RPC call types:

GetSong (unary). Returns a random song from the database.

(unary). Returns a random song from the database. AddSongs (client-side stream). Add multiple songs to the database.

(client-side stream). Add multiple songs to the database. GetChat (server-side stream). Returns the list of chat messages on a song.

(server-side stream). Returns the list of chat messages on a song. LiveChat (bidirectional stream). Engage in live discussion about a song, simultaneously sending chat messages and receiving them from others.

With a bit of meta-programming magic, we can compile our protobuffer service definition into JavaScript and the corresponding TypeScript type definitions. We use this fantastic tool, which conveniently produces type definitions for the RPC methods on both the server and client.

First, let’s install the required dependencies:

yarn add grpc grpc-tools grpc_tools_node_protoc_ts

Next, let’s create a build script in scripts/build-protos.sh :

Finally, we can run it with:

sh ./scripts/build-protos.sh ./songs.proto ./src/proto

Now you’ll find your generated code in src/proto :

songs_grpc_pb.js (JS code for interacting with RPC methods)

(JS code for interacting with RPC methods) songs_grpc_pb.d.ts (… and associated type definitions)

(… and associated type definitions) songs_pb.d.ts (JS code for interacting with message definitions)

(JS code for interacting with message definitions) songs_pb.js (… and associated type definitions)

Implementing the Server

Our generated code exposes a convenient interface which describes precisely how we can implement the gRPC server.

The callback function knows you need to pass it a Song

It’s full typed: both callback- and stream-based handlers use generic types to describe precisely the format of the messages we are passing around (defined, of course, in our service definition).

Once we’ve implemented all our RPC handlers, we can start up the server with something like:

const server = new grpc.Server(); server.addService<ISongsServer>(SongsService, new SongsServer()); console.log(`Listening on ${process.env.PORT}`); server.bind(`localhost:${process.env.PORT}`, grpc.ServerCredentials.createInsecure()); server.start();

Clean!

Implementing the Client

The generated code exposes a constructor to create a new gRPC client instance for our service:

import grpc from 'grpc';

import services from '../proto/songs_grpc_pb'; export default new services.SongsClient(/* ...args */);