sondahub

sondahub / gRPC-Web & Connect

gRPC-Web and Connect test server

Every mock API here is also a protobuf service: seven of them, with .proto files to generate clients from, unary calls over gRPC-Web and Connect — binary, text or JSON — and a server stream of live events. Free, no key, callable from a browser.

The services

One protobuf package per API — sondahub.{api}.v1 — with List, Get, Create, Update and Delete for every collection, and Watch, a server stream of the API's live events. Fields are proto3 optional where a value may be missing; JSON-shaped fields are google.protobuf.Value.

ServiceDefinition
sondahub.store.v1.StoreService46 methodsstore.proto
sondahub.fleet.v1.FleetService26 methodsfleet.proto
sondahub.bank.v1.BankService31 methodsbank.proto
sondahub.social.v1.SocialService26 methodssocial.proto
sondahub.helpdesk.v1.HelpdeskService26 methodshelpdesk.proto
sondahub.flights.v1.FlightsService21 methodsflights.proto
sondahub.identity.v1.IdentityService16 methodsidentity.proto
Base URL
https://api.sondahub.com/grpc
A method
https://api.sondahub.com/grpc/sondahub.store.v1.StoreService/GetProduct
gRPC-Web
application/grpc-web+proto, application/grpc-web-text
Connect
application/json, application/proto; application/connect+json or +proto for streams; GET for reads

Try it

Connect over GET
curl "https://api.sondahub.com/grpc/sondahub.store.v1.StoreService/GetProduct?encoding=json&message=%7B%22id%22%3A1%7D"
Connect JSON: the five dearest products in stock
curl -X POST https://api.sondahub.com/grpc/sondahub.store.v1.StoreService/ListProducts \
  -H "Content-Type: application/json" \
  -d '{"limit": 5, "sort": "-price", "filter": {"in_stock": "true"}}'
An error, the Connect way
curl -i "https://api.sondahub.com/grpc/sondahub.store.v1.StoreService/GetProduct?encoding=json&message=%7B%22id%22%3A999999%7D"
Native gRPC is refused, politely
grpcurl -proto store.proto -d '{"id": 1}' api.sondahub.com:443 sondahub.store.v1.StoreService/GetProduct
# the hub answers grpc-status 12 (UNIMPLEMENTED), the reason in grpc-message

From a browser or Node

Generate a client from the .proto with buf and protobuf-es, then point Connect's gRPC-Web transport at the hub:

import { createClient } from '@connectrpc/connect'
import { createGrpcWebTransport } from '@connectrpc/connect-web'
import { StoreService } from './gen/store_pb' // generated from store.proto by buf

const client = createClient(StoreService, createGrpcWebTransport({ baseUrl: 'https://api.sondahub.com/grpc' }))

const product = await client.getProduct({ id: 1n })
const page = await client.listProducts({ limit: 5, sort: '-price', filter: { in_stock: 'true' } })
for await (const e of client.watch({ topics: ['orders'], maxEvents: 5 })) console.log(e.topic, e.data)

int64 ids are bigint in protobuf-es. Writes keep to the session: send X-Sondahub-Session as call metadata, and read the new one from the response headers. CORS is open, so this runs from any page.

Questions

Why gRPC-Web and not native gRPC?

Native gRPC needs HTTP/2 trailers end to end, and the platform the hub runs on cannot send them. gRPC-Web and Connect were made for exactly that: the same calls and the same protobuf messages over plain HTTP, with the trailers in the body. A native gRPC call is answered UNIMPLEMENTED with a message saying so, rather than hanging.

Does server streaming work?

Yes: every service has Watch, the API's live activity as a server stream — each topic at its own pace (the Store's orders every 4 s, inventory every 6 s) until max_events (10 by default, up to 120). Over gRPC-Web and over Connect streaming (application/connect+json or +proto). Client and bidirectional streams need what native gRPC needs, so there are none.

Is server reflection available?

No — reflection is a native gRPC service. The .proto files are served instead, one per API, generated from the same descriptors that encode the wire, so they cannot drift.

Can I call it with curl?

Connect makes that easy: POST JSON with Content-Type: application/json to the method's path, or GET it with ?encoding=json&message=…. Errors come back as Connect errors, {"code": "not_found", "message": …}, with the matching HTTP status.