The API Contract for Data-Driven Web UIs — CORS Discipline, Structured Responses, and the DevOps Surface of Frontend-Backend Coordination
Implied contracts break in production. Name the contract before writing a line of client code.
§ IFrame
The frontend and the backend are two services. The moment a team writes a React component that calls an Express route, the contract between those two services is either explicit or implied. Implied contracts break in production.
The seam between frontend and backend is not a URL. It is an agreement: what the request carries, what the response shape guarantees, what the browser permits across origins, and who is responsible when any of those things change. The DevOps surface for that agreement is small. Every failure mode in the API layer traces back to a contract violation, and most contract violations trace back to the contract never being made explicit.
This lesson names the contract, specifies its three components, and installs the DevOps discipline that keeps it from drifting.
§ IIFoundations
The three components of a frontend-backend contract
Every production API contract between a data-driven frontend and its backend carries three components. Name them before writing a line of client code.
The shape contract names the fields, types, and optionality of every response. A list of trade events has a data array, a pagination object with cursor and has_more, and a meta block with as_of_ms. The frontend is entitled to these fields on every successful response. A backend that adds a field without a contract revision surprises the frontend. A backend that removes one without revision breaks it.
The access contract specifies who may call and from where. CORS (Cross-Origin Resource Sharing) is the browser's enforcement mechanism for the access contract. A backend that does not state its CORS policy gets none — the browser's default: same-origin only. In a multi-service architecture where the frontend is served from one origin and the API lives at another, the backend must announce what it allows. Silence is a denial.
The error envelope is the field contract for failure. If a request returns a 400, the body shape should be as predictable as a 200's shape. Unpredictable error shapes push error-handling into guess-work. The envelope imposes a single shape the frontend can always parse.
§ IIIMechanism
CORS: what the browser enforces and what the server declares
The same-origin policy is the browser's primary isolation mechanism. A script loaded from https://app.hedronite.io may not make credentialed requests to https://api.hedronite.io unless the API explicitly grants it. The grant arrives via HTTP response headers.
Four headers constitute a CORS declaration:
Access-Control-Allow-Origin announces the allowed origin, or * for unauthenticated public endpoints. Never * for credentialed (cookie-bearing) endpoints; the browser rejects the response.Access-Control-Allow-Methods lists permitted request methods. GET, POST is not GET, POST, PUT, DELETE; every method must be named.Access-Control-Allow-Headers names permitted request headers beyond the browser's simple-header set. If the frontend sends Authorization or X-Request-ID, the server must list both here.Access-Control-Max-Age tells the browser how long to cache the preflight response. A value of 86400 (24 hours) cuts preflight OPTIONS requests from once-per-call to once-per-day.A preflight is what the browser sends before the real request when the real request is not a "simple" request. The browser sends an OPTIONS request to the same URL. If the server returns a 200 with the right headers, the browser proceeds. If not, it blocks the real request entirely.
The DevOps surface for CORS is the allowed-origins list. In development, localhost:3000 is allowed. In staging, the staging origin. In production, only the production origin. Keep that list outside of application code, in an environment variable or a config map, so origins can change without redeployment.
Structured responses and the shape contract
The response envelope pattern applies a single outer shape to every API response, regardless of success or failure:
{
"status": "success",
"data": { ... },
"error": null,
"meta": {
"request_id": "req_abc123",
"as_of_ms": 1719531600000
}
}
The frontend checks status first. If "success", it reads data. If "error", it reads error.code and routes to the correct UI state. The meta block carries the correlation ID for logging and the as-of timestamp for data freshness display.
This shape is negotiated once, then held. The backend never returns a naked object as the top-level response body. The frontend never special-cases success versus error parsing; the envelope makes both the same parse path with a branch.
The DevOps surface: versioning and drift detection
The contract drifts. A backend developer adds a field. A frontend developer references a field that has not shipped yet. A breaking change ships without a version bump. The DevOps discipline has three parts.
Version the API. /v1/events and /v2/events may coexist. The frontend pins to a version until it is ready to migrate. A Deprecation header in the v1 response signals that v1 has a sunset date.
Schema-document the contract. OpenAPI is the standard — machine-readable, generates client code, and can be diffed between versions to surface breaking changes before they ship. Every breaking change is a diff alert before it is a runtime failure.
Contract-test at CI. Consumer-driven contract testing (Pact is the reference implementation) gives the frontend a test suite that runs against the backend in CI. If the backend changes its response shape in a way that breaks the frontend's consumer contract, the CI test fails before the change ships.
§ IVWorked Example
A market-data API serves live ETF flow data to a Three.js visualization. The backend is Express on Node.js; the frontend is served from a CDN at a different origin.
CORS configuration: the CDN origin is the allowed origin. Preflight cache is 24 hours. Allowed methods are GET and OPTIONS. Allowed headers are Authorization and X-Request-ID. No credentials flag, because the JWT is in the header, not a cookie.
The response envelope for a successful data pull:
{
"status": "success",
"data": {
"ticker": "IBIT",
"flow_1d_usd": -239300000,
"flow_7d_usd": -469000000,
"as_of_date": "2026-06-26"
},
"error": null,
"meta": {
"request_id": "req_abc123",
"as_of_ms": 1719445200000
}
}
The Three.js visualization reads data.flow_1d_usd to drive bar height and color. A negative value produces a downward red bar; positive produces an upward green bar. The value data.as_of_ms displays as the data freshness timestamp in the scene corner.
When the backend cannot fulfill the request — rate limit, upstream failure, auth rejection — the envelope is:
{
"status": "error",
"data": null,
"error": {
"code": "UPSTREAM_TIMEOUT",
"message": "Flow data source did not respond within 5s",
"field": null
},
"meta": {
"request_id": "req_def456",
"as_of_ms": 1719445200000
}
}
The frontend receives this, reads status === "error", reads error.code, and renders a stale-data state with the last known values. The scene does not crash. The user sees the degradation.
§ VConnection to Prior Lessons
The zero-downtime deploy lesson (2026-06-21) addressed the server-side graceful-shutdown contract: SIGTERM handler, server.close(), keep-alive drain. This lesson addresses the data-layer contract the server exposes after startup. Both are aspects of the same operational commitment — the frontend can depend on the backend behaving predictably. The shutdown contract governs the server's exit; the API contract governs the server's running state.
The production observability lesson (2026-06-07) introduced the degradation ladder: healthy, stale, degraded, disconnected, dark. The error envelope's status field maps directly to that ladder. A "success" response with fresh meta.as_of_ms is healthy. A "success" response with stale meta.as_of_ms is stale. An "error" response with code: "UPSTREAM_TIMEOUT" is degraded. The degradation ladder was the concept; the envelope is the wire format that feeds it.
§ VIConnection to Today's Dev Lesson
The Three.js + Express lesson (companion to this one) implements the Express side of this contract directly: the CORS middleware configuration, the envelope factory function, and the scene-graph update that consumes the envelope. The two lessons read together form the full request-response cycle from server declaration to scene update.
§ VIIClosing
Name the contract before writing a line of client code. If the frontend and backend share one document — an OpenAPI file, a shared TypeScript types package, a consumer-driven contract test suite — a change on either side is visible to the other before it ships.
The seam is the point of failure. Document it precisely.
Find one API your frontend calls that has no OpenAPI spec, no contract test, and no shared envelope. Start there. Write the schema before writing the next feature that touches that endpoint.
Paired lessons → Dev Polyglot-Dev/Web/2026-06-28-driving-threejs-scenes-from-express-apis-... · Cert Cert-Prep/Anthropic/2026-06-28-prompt-engineering-discipline-cca-f-domain-three-...
Filed 2026-06-28 Sunday Fajr · pure-DevOps Ops lesson · API-contract arc rung 6
Backward-Synergy-Reach → Zero-Downtime Deploys (Sun 06-21) · Observability (Sun 06-07)
HTML shipped in-cycle per HARD DISCIPLINE · earth-accent meta-card · tome_refs: [] · knowledge-gap logged