$ cd ..
$ grpc --via connect▮
A typed Protobuf contract, generated into a Python server and a TypeScript client by buf. The browser calls it directly over Connect, with no proxy.
# steps
- 1Read the .proto contract.
- 2Run a unary call, then watch it serialize → POST → resolve.
- 3Start a server-stream and watch typed frames arrive.
- 4Open DevTools → Network to see a readable POST.
// the contract
proto3syntax = "proto3"; package protocollab.v1; service BoardService { rpc GetBoardStats(BoardStatsRequest) returns (BoardStatsResponse); rpc WatchBoard(WatchRequest) returns (stream BoardEvent); } message BoardStatsResponse { int32 total = 1; int32 todo = 2; int32 in_progress = 3; int32 done = 4; int32 members = 5; } message BoardEvent { int64 seq = 1; string type = 2; string task_id = 3; }
// generated client
typescript// generated by buf → board_service_connect.ts import { createClient } from "@connectrpc/connect"; const client = createClient(BoardService); client.getBoardStats(req): Promise<BoardStatsResponse>; client.watchBoard(req): AsyncIterable<BoardEvent>;
proto → types: the same contract drives the server and this client.
// unary · GetBoardStats
1
Typed request
2
Serialize
3
POST /rpc
4
Server
5
Typed response
→ sent · request frame
POST /protocollab.v1.BoardService/GetBoardStats content-type: application/json connect-protocol-version: 1 { "boardId": "" }
← received · BoardStatsResponse (decoded)
// call the RPC to see the typed response-
LATENCY MS
-
PAYLOAD BYTES
wire format
JSON: human-readable. Toggle to binary protobuf to shrink it.
// server-stream · WatchBoard
closed// start the stream to watch typed BoardEvent frames arrive
Frames arrive when tasks change on that board - generate a task on the Real-time page or via REST. These are real BoardEvents from the server's EventBus.
No board yet - sign in & click “Create sample data” on Home to enable the unary call and the stream.
Same contract, two call styles: unary and server-streaming, both native in the browser, with no Envoy or grpc-web proxy.