Skip to main content
Deno 2 is finally here 🎉️
Learn more

/x/grpc_basic

⚠️ You probably should wait for more mature and standard aligned implementation beacuse:

  1. This lib doesn’t use Deno’s 1.9 HTTP/2 native bindings, but relies on JS implementation roughly ported from node-http2
  2. I’m not an expert in gRPC or HTTP/2, I just moved HTTP/2 frames around until it worked
  3. It was never meant for production use, only for fun and some integration tests and scripts
  4. I have no plans on implementing full gRPC spec

goals - keep it simple

  • load proto files
  • server unary calls
  • client unary calls
  • multiplex calls
  • server server streams
  • client server streams
  • context deadlines
  • logging interface

non goals - gRPC bloat

  • no TLS
  • no client streams
  • no bidirectional streams
  • no load balancers (maybe simple helper for proxy lb)
  • no interceptors (revisit this later)

hello world

greeter.proto

syntax = "proto3";

package prohazko;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  rpc ShoutHello (HelloRequest) returns (stream HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

greeter.d.ts

Service typings are not essential, but it’s nice to have them

$ deno run --allow-read https://deno.land/x/grpc_basic@0.4.1/gen/dts.ts ./greeter.proto > ./greeter.d.ts
export interface Greeter {
  SayHello(request: HelloRequest): Promise<HelloReply>;
  ShoutHello(request: HelloRequest): AsyncGenerator<HelloReply>;
}

export interface HelloRequest {
  name?: string;
}

export interface HelloReply {
  message?: string;
}

server.ts

import { GrpcServer } from "https://deno.land/x/grpc_basic@0.4.1/server.ts";
import { Greeter } from "./greeter.d.ts";

const port = 15070;
const server = new GrpcServer();

const protoPath = new URL("./greeter.proto", import.meta.url);
const protoFile = await Deno.readTextFile(protoPath);

server.addService<Greeter>(protoFile, {
  
  async SayHello({ name }) {
    const message = `hello ${name || "stranger"}`;
    return { message };
  },

  async *ShoutHello({ name }) {
    for (const n of [0, 1, 2]) {
      const message = `hello ${name || "stranger"} #${n}`;
      yield { message };
    }
  }
});

console.log(`gonna listen on ${port} port`);
for await (const conn of Deno.listen({ port })) {
  server.handle(conn);
}

client.ts

import { getClient } from "https://deno.land/x/grpc_basic@0.4.1/client.ts";
import { Greeter } from "./greeter.d.ts";

const protoPath = new URL("./greeter.proto", import.meta.url);
const protoFile = await Deno.readTextFile(protoPath);

const client = getClient<Greeter>({
  port: 15070,
  root: protoFile,
  serviceName: "Greeter",
});

/* unary calls */
console.log(await client.SayHello({ name: "unary #1" }));
console.log(await client.SayHello({ name: "unary #2" }));

/* server stream */
for await (const reply of client.ShoutHello({ name: "streamed" })) {
  console.log(reply);
}

client.close();