Using clients
We have already been using the function createClient
in the tutorial. The
function gives us a client that uses ECMAScript promise objects. In combination
with the await
keyword, this lets you write asynchronous
code in a natural and easily readable way:
import { createClient } from "@connectrpc/connect";
import { ElizaService } from "@buf/connectrpc_eliza.bufbuild_es/connectrpc/eliza/v1/eliza_pb";
const client = createClient(ElizaService, transport);
const res = await client.say({
sentence: "I feel happy.",
});
console.log(res.sentence);
For server-streaming RPCs, the corresponding method on the client will return
an async iterable stream of response messages that can be used with the
for await...of
statement:
for await (const res of client.introduce({ name: "Joseph" })) {
console.log(res);
}
Callbacks
If you prefer a callback-based approach, the client returned by the function
createCallbackClient
should suit you:
import { createCallbackClient } from "@connectrpc/connect";
import { ElizaService } from "@buf/connectrpc_eliza.bufbuild_es/connectrpc/eliza/v1/eliza_pb";
const client = createCallbackClient(ElizaService, transport);
client.say({ sentence: "I feel happy." }, (err, res) => {
if (!err) {
console.log(res.sentence);
}
});
For server-streaming RPCs, the corresponding method on the client takes two callback functions: one that is called every time a response message arrives, and one that is called at the end of the stream.
import {ConnectError} from "@connectrpc/connect";
client.introduce({name: "Joseph"}, (res) => {
console.log(res);
}, (err?: ConnectError) => {
if (err) {
console.error(err);
}
});
The callback client is particularly useful if you want to migrate an existing code base from gRPC-web to Connect clients.
Managing clients and transports
In practice, you will likely want to avoid creating a new transport every time you want to use a client. It really depends on the framework of your choice, but there usually is a simple solution to avoid repetition.
For example, you can easily create a custom hook in React:
// use-client.ts
import { useMemo } from "react";
import { type DescService } from "@bufbuild/protobuf";
import { createConnectTransport } from "@connectrpc/connect-web";
import { createClient, type Client } from "@connectrpc/connect";
// This transport is going to be used throughout the app
const transport = createConnectTransport({
baseUrl: "https://demo.connectrpc.com",
});
/**
* Get a promise client for the given service.
*/
export function useClient<T extends DescService>(service: T): Client<T> {
// We memoize the client, so that we only create one instance per service.
return useMemo(() => createClient(service, transport), [service]);
}
Usage:
await useClient(ElizaService).say({sentence: "I feel happy."});
Roll your own client
If you find that neither client suits your needs perfectly, it might be an option for you to roll your own. For example, you might prefer Rust-style result types over promise rejections, and could write your own constructor function that uses the neverthrow library for method return values.
For a working example, see the custom-client
in the examples-es repo, or take
a look at the implementation of createClient
and createCallbackClient
.