gRPC-Web
Call Yatta services from a browser over plain HTTP, with real protobuf messages. No extra setup.
Browsers cannot speak raw gRPC, but they can speak gRPC-Web: the same protobuf messages, sent over a normal HTTP POST. Yatta serves it on /grpc-web/<Service>/<Method>. Only single-request, single-reply calls work — no streaming.
Call Echo
The built-in Echo service sends your message straight back. This example needs no library: it builds the frame by hand.
// A message frame: 1 flag byte, 4 length bytes (big-endian), then the message.function frame(message) { const out = new Uint8Array(5 + message.length); out[0] = 0; out[1] = (message.length >> 24) & 0xff; out[2] = (message.length >> 16) & 0xff; out[3] = (message.length >> 8) & 0xff; out[4] = message.length & 0xff; out.set(message, 5); return out;} // A string in field 1: tag 0x0a, length, then the UTF-8 bytes.function stringField(text) { const bytes = new TextEncoder().encode(text); const out = new Uint8Array(2 + bytes.length); out[0] = 0x0a; out[1] = bytes.length; out.set(bytes, 2); return out;} const res = await fetch("http://localhost:4000/grpc-web/yatta.Echo/Echo", { method: "POST", headers: { "Content-Type": "application/grpc-web" }, body: frame(stringField("hello")),}); const buf = new Uint8Array(await res.arrayBuffer());const len = buf[6]; // reply message, field 1console.log(new TextDecoder().decode(buf.slice(7, 7 + len))); // "hello"console.log(res.headers.get("grpc-status")); // "0"Check health
The Health service ignores what you send and answers SERVING in field 1. An empty message frame is enough.
const res = await fetch("http://localhost:4000/grpc-web/yatta.Health/Check", { method: "POST", headers: { "Content-Type": "application/grpc-web" }, body: frame(new Uint8Array(0)),}); const buf = new Uint8Array(await res.arrayBuffer());const len = buf[6];console.log(new TextDecoder().decode(buf.slice(7, 7 + len))); // "SERVING"Services on the shipped server
yatta.Echo/Echoyatta.Health/CheckOnly POST works. Any other method, or a path that is not /grpc-web/<Service>/<Method>, answers 404.
The frame format
Every message travels in a frame. The reply is a message frame followed by a trailer frame that carries the status.
message frame: byte 0: compression flag, always 0 bytes 1-4: message length, big-endian bytes 5+: the protobuf message trailer frame: same shape, flag byte set, carrying grpc-status string field (field 1): 0x0a, length, UTF-8 bytes number field (field 2): tag byte (field number shifted left 3), then the varint valueErrors
Errors still answer HTTP 200. The real code is in the grpc-status header and the trailer frame.
12313