gRPC / RPC

Type-safe gRPC services with proto imports - no codegen required

The @justscale/rpc package provides gRPC transport for contract-based controllers. Import contracts directly from .proto files - no code generation step required!

Installation

Bash
pnpm add @justscale/rpc @justscale/protobuf

Key Features

  • Direct proto imports - Import types directly from .proto files
  • No codegen - TypeScript compiler plugin generates types at build time
  • Contract-based - Define controllers that implement gRPC service contracts
  • DI integration - Full dependency injection support for clients and servers
  • All streaming modes - Unary, server streaming, client streaming, bidirectional
  • Built-in features - Health checks, reflection, interceptors, compression

Quick Example

Define a proto file with your service contract:

protobuf
// greeter.proto
syntax = "proto3";
package greeter;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

Import and implement the contract in a controller:

TypeScript
import { GreeterContract } from './greeter.proto'
import { createController } from '@justscale/core'

const GreeterController = createController
  .implements(GreeterContract)
  .create({
    inject: {},
    methods: () => ({
      SayHello: async ({ body }) => ({
        message: `Hello, ${body.name}!`,
      }),
    }),
  })

Serve via cluster:

TypeScript
import JustScale from '@justscale/core'
import '@justscale/rpc' // Auto-registers transport

const app = JustScale()
  .add(GreeterController)
  .build()

await app.serve({
  rpc: { port: 50051, enableReflection: true },
})

Streaming RPCs

All gRPC streaming modes are supported. Use async generators for streaming responses:

TypeScript
// Server streaming
async *StreamGreetings({ body }) {
  for (let i = 0; i < body.count; i++) {
    yield { message: `Hello #${i + 1}, ${body.name}!` }
    await delay(1000)
  }
}

// Bidirectional streaming
async *Chat({ body: messages }) {
  for await (const msg of messages) {
    yield { reply: `Echo: ${msg.text}` }
  }
}

Type-Safe Clients

Create DI-integrated clients with defineRpcClient:

TypeScript
import { defineRpcClient } from '@justscale/rpc'
import { GreeterContract } from './greeter.proto'

const GreeterClient = defineRpcClient({
  contract: GreeterContract,
  inject: { settings: GreeterSettings },
})

// Use in services
class MyService extends defineService({
  inject: { greeter: GreeterClient },
  factory: ({ greeter }) => ({
    async greet(name: string) {
      const reply = await greeter.SayHello({ name })
      return reply.message
    },
  }),
}) {}