gRPC with Typescript and Go (part 3)


In part 2 we implemented the service. Let’s write a Typescript client to complete the app.

Geting the Typescript setup out of the way

$ tsc --init
$ npm i --save grpc-web-client
$ npm i --save-dev webpack@4 webpack-cli@4 awesome-typescript-loader typescript @types/google-protobuf

Quick webpack config for transpilation to Javascript

const webpack = require('webpack');

module.exports = function makeWebpackConfig() {
    let config = {};

    config.entry = {
        index: "./index.ts",

    config.devtool = 'eval-source-map';

    config.resolve = {
        extensions: [".ts", ".js"]

    config.module = {
        rules: [
            {test: /\.ts?$/, loader: "awesome-typescript-loader"}

    return config;

Write an index.html and index.ts that will serve as our app

<div id="root"></div>
<script src="dist/index.js"></script>

document.getElementById('root').innerHTML = "Hi from TS";

To prevent issues with CORS, the Go service will serve both gRPC and our frontend static resource..

Jumping back to main.go, if a request is for gRPC, grpc-web will automatically add the Content-Type: application/grpc. Everything else we can treat as a request for static resources.

The http.Server handler in main.go would look something like this

if err := (&http.Server{
    Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
            wrappedGrpcServer.ServeHTTP(w, r)
        } else {
            http.FileServer(http.Dir(".")).ServeHTTP(w, r)
    Addr:    "",
}).ListenAndServe(); err != nil {

Quick test that the static resources are served correctly on http://localhost:9999/index.html

$ ./node_modules/.bin/webpack -w &
$ go run main.go

hi from ts

Typescript gRPC call

Always saving the best for last, lets call GetLogs with the browser and display the result.

import {GetLogsRequest, GetLogsResponse} from './generated/service_pb';
import {LogServiceClient, ServiceError} from './generated/service_pb_service';

let root = document.getElementById('root');

let client = new LogServiceClient('http://localhost:9999'),
    request = new GetLogsRequest();

client.getLogs(request, (err: ServiceError | null, logs: GetLogsResponse | null) => {
    if (!root) {
        throw new Error('Failed to find root');
    if (err) {
        root.innerHTML = 'Error: ' + err.message;
    if (!logs) {
        root.innerHTML = 'No logs';

    logs.getDataList().map(log => {
        if (!root) {
            throw new Error('Failed to find root');
        let pEl = document.createElement('p');
        pEl.innerHTML = log;

Which outputs