Skip to main content

0002. OpenAPI 3.0.3 contract with generated Go and TypeScript code

  • Status: Accepted
  • Date: 2026-09-30

Context

The backend (Go) and frontend (TypeScript) are built by different agents at the same time. Both sides need the same request, response and WebSocket message types. Types written by hand on each side drift apart, and a mismatch in a price or size field can place a wrong order.

Decision

  • api/openapi.yaml is the single source of truth for REST endpoints and WebSocket messages. WebSocket messages are defined as JSON Schema components in the same file.
  • The spec uses OpenAPI 3.0.3, not 3.1, because oapi-codegen only partly supports 3.1.
  • task gen generates:
    • the Go strict server and types with oapi-codegen into backend/internal/httpapi/gen;
    • the TypeScript types with openapi-typescript, used through openapi-fetch, into frontend/src/lib/api/gen.
  • Generated code is never edited by hand. To change a type, edit the spec and run task gen.
  • Contract changes land in their own PR before the code that uses them. Each phase starts with the contract diff.
  • Wire rules: snake_case field names, RFC 3339 UTC timestamps, money, prices and sizes as decimal strings, never a bare mode field.

Consequences

  • Frontend and backend work in parallel against the same generated types from the start of a phase.
  • A contract mismatch becomes a compile error on one side instead of a runtime bug.
  • 3.1-only features are not available. Nullable fields use nullable: true, and JSON Schema keywords added in 3.1 cannot be used.
  • The codegen tool versions must be pinned so that every machine generates the same code.
  • Contract changes go through the tech lead, which adds a step to changes that touch both sides.

Alternatives considered

  • OpenAPI 3.1. Aligns with current JSON Schema, but oapi-codegen support is incomplete, so generated Go code would be unreliable.
  • Hand-written types on each side. No tooling, but the two sides drift.
  • gRPC or Connect with protobuf. Strong typing, but the browser needs an extra transport layer, and payloads are harder to inspect in browser dev tools.
  • Generate TypeScript from Go types. Makes Go the source of truth and leaves no reviewable contract file for parallel work.