Calling from the browser (connect-web)
The connect-kotlin-server serves your services over the Connect protocol and gRPC-Web, making them callable from browser clients. This guide shows how to build a TypeScript client using @connectrpc/connect-web.
Overview
Section titled “Overview”A browser client using connect-web:
- Generates TypeScript clients from your
.protofiles usingbufandprotoc-gen-es - Builds a transport with either
createConnectTransport(Connect protocol) orcreateGrpcWebTransport(gRPC-Web) - Creates a client bound to the transport
- Calls unary and server-streaming methods just like a backend client
The transport can use proto-binary (application/proto for Connect, application/grpc-web+proto for gRPC-Web) or JSON encoding—toggled by a single option.
Generate the client
Section titled “Generate the client”Create a buf.gen.yaml in your client directory that points to your proto files:
version: v2inputs: - directory: ../path/to/protoplugins: - local: protoc-gen-es out: src/gen opt: - target=tsThen generate:
buf generateThis outputs TypeScript client stubs to src/gen/. For the example service:
import { EchoService } from "@gen/cgardev/example/v1/echo_pb";Build a transport
Section titled “Build a transport”Create transport instances for either protocol. Each transport takes a base URL and an encoding option.
import { createConnectTransport, createGrpcWebTransport,} from "@connectrpc/connect-web";import type { Transport } from "@connectrpc/connect";
const baseUrl = "http://localhost:8080";
// Connect protocol with proto-binary encodingexport function connectTransport(): Transport { return createConnectTransport({ baseUrl, useBinaryFormat: true, });}
// Connect protocol with JSON encodingexport function connectTransportJson(): Transport { return createConnectTransport({ baseUrl, useBinaryFormat: false, });}
// gRPC-Web protocol with proto-binary encodingexport function grpcWebTransport(): Transport { return createGrpcWebTransport({ baseUrl, useBinaryFormat: true, });}
// gRPC-Web protocol with JSON encodingexport function grpcWebTransportJson(): Transport { return createGrpcWebTransport({ baseUrl, useBinaryFormat: false, });}Both protocols are fully supported by the server over all encoding combinations.
[!TIP] Use proto-binary for efficiency in production; JSON for human readability during development.
Create a client and call methods
Section titled “Create a client and call methods”Create a client by passing the service and transport to createClient:
import { createClient } from "@connectrpc/connect";import { EchoService } from "@gen/cgardev/example/v1/echo_pb";import { connectTransport } from "./transport";
const transport = connectTransport();const client = createClient(EchoService, transport);Unary calls
Section titled “Unary calls”Call unary methods directly and await the response:
// Unary call (Echo)const response = await client.echo({ message: "hello" });console.log(response.message); // "echo: hello"
// Idempotent call (GET-eligible on the server)const info = await client.getServerInfo({});console.log(info.name); // "connect-kotlin-server"console.log(info.version);Server-streaming calls
Section titled “Server-streaming calls”Server-streaming methods return an async iterable. Iterate to consume the stream:
// Server-streaming call (Count)const numbers: number[] = [];for await (const response of client.count({ to: 5 })) { numbers.push(response.number);}console.log(numbers); // [1, 2, 3, 4, 5]Error handling
Section titled “Error handling”Errors thrown by the server are converted to ConnectError with full code and message:
import { ConnectError, Code } from "@connectrpc/connect";
try { await client.fail({ reason: "kaboom" });} catch (error) { if (error instanceof ConnectError) { console.log(error.code); // Code.InvalidArgument console.log(error.rawMessage); // "kaboom" }}Complete example
Section titled “Complete example”import { createClient, ConnectError, Code } from "@connectrpc/connect";import { EchoService } from "@gen/cgardev/example/v1/echo_pb";import { connectTransport } from "./transport";
async function main() { const transport = connectTransport(); const client = createClient(EchoService, transport);
// Unary const echoResponse = await client.echo({ message: "hi" }); console.log("Echo:", echoResponse.message);
// Server-streaming console.log("Counting:"); for await (const response of client.count({ to: 3 })) { console.log(" ", response.number); }
// Error handling try { await client.fail({ reason: "test error" }); } catch (error) { if (error instanceof ConnectError) { console.log("Error:", error.code, error.rawMessage); } }}
main().catch(console.error);Testing: the e2e-connect-web suite
Section titled “Testing: the e2e-connect-web suite”The repository includes tools/e2e-connect-web, a Vitest end-to-end suite that:
- Starts the server (
bootRun) automatically - Generates the client from proto using
buf - Exercises 16 test cases: 4 RPC methods × 2 protocols (Connect, gRPC-Web) × 2 encodings (proto-binary, JSON)
Run the tests:
cd tools/e2e-connect-webpnpm installpnpm testThis validates that your setup is correct and the server handles all combinations properly.
The server ships with permissive CORS enabled by default for browser clients. If you need to restrict it, see Configuration.