API infrastructure

Four protocols,
one set of facts.

The same projects, served as REST with a written specification, as a GraphQL subgraph with the limits that make one safe to expose, and as protobuf on the wire. The console below builds itself from the specification, so the two cannot drift.

Try it, for real.

Requests go from your browser to this origin. Nothing is mocked and nothing is replayed — the timings and the bytes are what the endpoint actually returned.

Reading the specification…

Read-only. Writes are gated behind the lab.sandbox-writes flag, which evaluates to false for an anonymous visitor — reason no-rule-matched. A public write console on a live site is an abuse endpoint with a pleasant interface, and /api/contact would put a stranger’s message in a real inbox.

The specification, written by hand.

Generating it from the zod schemas would guarantee the request shapes never drift, and it is the better answer for exactly that. It is also where generated specs stop: nothing derives a description, an example, or the reason an endpoint returns 409. A generated document is complete and says nothing. So request bodies name the schema that defines them, the prose is written, and a test asserts every documented path still exists as a route — which catches the drift that actually happens.

Documented
8paths, all verified to exist
Version
3.1a strict JSON Schema superset
protobuf
103 Bvs 161 B as JSON
Saving
36%on this payload

That saving is real and also the least interesting property of protobuf. Gzip closes most of the gap on a payload this size; what a schema actually buys is that field 3 is field 3 forever, so a reader built last year decodes a message written today and skips the fields it has never heard of. The document and the .proto are both served.

GraphQL, and the limits that make it safe.

A parser, a schema walker and an executor, written rather than installed. The subset is the honest part: queries, nesting, aliases and scalar arguments are supported; mutations, subscriptions, fragments, variables and directives are refused by name. A parser that silently ignores a fragment returns a response missing fields the caller asked for, and the caller cannot tell that from data that is genuinely absent.

Depth is capped at 6

GraphQL’s defining denial-of-service surface is that a few hundred bytes of nesting can demand unbounded server work. Both this and the 60-field ceiling are checked before a single resolver runs, and the measured cost of every query comes back in extensions.

Read-only by construction

Everything mutable here already has a REST route with its own CSRF check, rate limit and audit trail. Duplicating those guarantees in a second protocol doubles the number of places a mistake can happen, for no capability the site did not already have.

Always 200

Including when fields fail. That is the contract and it surprises people: the status describes the transport, the errors array describes the query. Field errors are collected and the field set to null, so a partial response survives — throwing on the first failing resolver would discard everything that succeeded.

The SDL is generated

A federation gateway asks a subgraph for _service { sdl } and composes the supergraph from the answers. Rendering it from the same schema object the executor walks means the two cannot disagree — unlike a checked-in SDL file sitting beside a hand-written resolver map.

The subgraph SDL
type Query {
  """Every published project, newest first."""
  projects: [Project!]!
  """One project by slug. Null when it does not exist."""
  project: Project
  """Every tag used across the project set."""
  tags: [String!]!
}

type Project {
  slug: String!
  title: String!
  category: String!
  summary: String!
  outcome: String!
  """Technologies the project is built on."""
  stack: [String!]!
  tags: [String!]!
}
The .proto descriptor
syntax = "proto3";

package shivamsfolio.stats.v1;

message StatsRequest {
  string source = 1;
  int32 limit = 2;
}

message StatsResponse {
  string source = 1;
  int32 total = 2;
  repeated string names = 3;
  repeated int32 scores = 4;
  double ratio = 5;
  bool stale = 6;
}

service Stats {
  rpc Get(StatsRequest) returns (StatsResponse);
}

What is not here.

gRPC, properly

gRPC is HTTP/2 with trailers and a framing browsers cannot originate and this runtime does not expose. gRPC-Web exists because of that gap and needs a translating proxy — the blueprint’s Envoy. What is implemented is the protobuf wire format itself, by hand, carried over a POST with Connect’s framing. The content type says application/proto, not application/grpc, so a real gRPC client fails at negotiation rather than deep inside its framing code.

A federation gateway

Federation is composition across several subgraphs, and there is one service here. What is implemented is the subgraph half of the contract — the SDL a gateway would ask for. A gateway composing one subgraph is an expensive proxy.

Flags in a database

The evaluator is pure and the inventory lives in code, so a flag can be found by grepping — the worst property of a flag system is that dead flags accumulate invisibly until nobody dares delete any. Values from a store would layer on top; the evaluation would not change, which is the whole reason it is separated.

Generated clients

The document is complete enough for openapi-generator and the descriptor for protoc, and neither is run here. At three consumers, hand-written calls are less machinery; at thirty, generated clients are obviously right, and this is the wrong scale for them.