Skip to Content
🎉 gRPCity 3.0 is released. Read more →

Client

The clients factory in gRPCity discovers services from the loader, dials them, and hands you a typed proxy you can call like an ordinary async function.

Initialization

const clients = await loader.initClients({ services, channelOptions, credentials })

Parameters:

  • services (required) — { [serviceName]: address }
    • serviceName: fully qualified service name, including the package.
    • address: server address. Either host:port (string) or { host, port }.
  • channelOptions (optional) — channel-level configuration passed through to gRPC.
  • credentials (optional) — client credentials, e.g. for TLS.

Example:

const credentials = loader.makeClientCredentials(rootCerts, privateKey, certChain, verifyOptions) const clients = await loader.initClients({ services: { 'dev.path.to.serviceA': 'domain.local:9099', 'path.to.serviceB': { host: '192.168.32.111', port: 10099 } }, channelOptions, credentials })

Middleware

clients.use() runs around every RPC, before and after the call. See the Client Middleware guide.

// add one at a time clients.use(f1) clients.use(f2) clients.use(f3) // or pass several positional middlewares clients.use(f1, f2, f3) // or pass an array clients.use([f1, f2, f3])

Middleware only runs around async calls. The callback form bypasses it.

Getting an instance

get(name) returns a cached client proxy for the given fully qualified service name.

const serviceAClient = clients.get('dev.path.to.serviceA') const serviceBClient = clients.get('path.to.serviceB')

get() caches by name, so repeated calls return the same instance. Use getReal() if you need a fresh, uncached client.

Making an RPC call

Two styles are supported: async and callback. The async form is the default.

async

const result = await serviceAClient.rpcMethod(request, metadata, options)

Arguments:

  • rpcMethod — the method declared on the service in your .proto. There are four kinds: unary, client streaming, server streaming, and bidi streaming.
  • request — the request payload, shaped after the message type from the proto.
  • metadata (optional) — request metadata as key/value pairs (auth, tracing, etc.).
  • options (optional) — per-call options. Recognised fields include timeout (per-call deadline; defaults to 10 seconds when omitted) and signal (an AbortSignal to cancel the call — see AbortSignal).

Unary example:

const meta = loader.makeMetadata({ 'x-business-id': ['grpcity', 'testing'], 'x-timestamp-client': 'begin=' + new Date().toISOString() }) const options = { timeout: 5000 // 5 seconds from now } const { status, peer, metadata, response } = await serviceAClient.rpcMethod( { name: 'myapp' }, meta, options )

Every call resolves to four fields:

  • status — the gRPC status returned by the server.
  • peer — the remote address you talked to.
  • metadata — trailing metadata returned by the server.
  • response — the response payload itself.

A typical result looks like:

{ status: { code: 0, details: 'OK', metadata: Metadata { internalRepr: Map(0) {}, options: {} } }, peer: '::9098', metadata: Metadata { internalRepr: Map(2) { 'content-type' => [Array], 'date' => [Array] }, options: {} }, response: { message: 'hello greeter by Greeter in server1', count: 1 } }

callback

The callback form sits behind .call.:

const a = serviceAClient.call.rpcMethod1({ name: 'myapp' }, (err, result) => { if (err) throw err return result }) const b = serviceBClient.call.rpcMethod2({ name: 'youapp' }, (err, result) => { if (err) throw err return result })

Reinitialization

When server addresses change at runtime, clear the existing clients first, then re-init with the new mapping.

const clients = await loader.initClients({ services: { 'dev.path.to.serviceA': 'domain.local:9099', 'path.to.serviceB': { host: '192.168.32.111', port: 10099 } } }) clients.clear() clients.init({ services: { 'dev.path.to.serviceA': 'domain.local:9088', 'path.to.serviceB': { host: '192.168.32.112', port: 10099 } } })

clear() must run before init(). Otherwise the new init returns the cached factory and the address swap silently has no effect.

Last updated on