Streaming, four ways
Unary, client stream, server stream, and bidi — all consumable with for await. Throwing in a handler always reaches the client as a status.
A small, opinionated framework that wraps @grpc/grpc-js and @grpc/proto-loader so you can ship a typed service or client in a handful of lines.
npm i grpcityimport { loader } from './loader.js'
class Greeter {
async sayGreet(call) {
return { message: `hello, ${call.request.name}` }
}
}
await loader.init()
const server = await loader.initServer()
server.add('helloworld.Greeter', new Greeter())
await server.listen('127.0.0.1:9099')Every gRPCity release ships with the features below on, behind a stable API.
Unary, client stream, server stream, and bidi — all consumable with for await. Throwing in a handler always reaches the client as a status.
Cancel any RPC by passing an AbortSignal. Pre-abort short-circuits the call; mid-flight abort cancels and surfaces as CANCELLED.
Koa-style (ctx, next) for clients and servers — log, trace, retry, mutate metadata, all without touching the call site.
loader.initReflection() returns a service you can inject() straight into the server, so grpcurl and Postman just work.
Helpers (makeServerCredentials, makeClientCredentials) hide the boilerplate; full mTLS in a few function calls.
Loader, client, and server options are validated at runtime with zod — typos fail loudly, not silently.
Written in TypeScript end to end. Types for Metadata, credentials, ChannelCredentials, StatusObject, and more re-exported from the entry point.
Use await everywhere by default; callback variants stay around for legacy integrations and event-driven hot paths.
gRPCity sits on top of @grpc/grpc-js — it does not replace it. The point of the wrapper is to make the parts you write every day shorter and the parts you forget about safer.
signal to any RPC — pre-abort and mid-call abort just workfor await (const m of call.readAll()) everywhereError payload, no typed shapeGrpcClientError with code / details / metadata