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);
}