Skip to content

committed openapi.json drifts from the backend with no CI regen gate: generated client types silently lie #434

Description

@kilodesodiq-arch

Problem

The frontend's client types are generated from a committed snapshot (app/frontend/openapi.json) with no automated check that the snapshot matches the backend's live spec. package.json provides only a manual regen:

// app/frontend/package.json
"generate:api": "openapi-typescript openapi.json -o src/lib/generated/api.ts"

The backend can export its spec (spec:export in app/backend/package.json runs scripts/export-spec.ts), but nothing links the two in CI. If the backend changes a route, DTO, or status code, openapi.json stays stale and src/lib/generated/api.ts continues to compile against the old contract.

Consequence: the typed client silently lies. A frontend developer sees a correctly-typed request that 400s (or silently misinterprets a field) at runtime because the generated types predate the backend change. This is the exact failure mode that makes a generated client worse than untyped code — false confidence — and it cannot be caught by pnpm type-check because the types are self-consistent against the stale spec.

Root cause

The spec export (backend) and type generation (frontend) are two unconnected scripts; the committed openapi.json is the single point of drift with no CI reconciliation.

Why this is architecturally hard

  1. The drift gate must run across two packages. The backend must export a fresh spec in CI, then the frontend must regenerate and diff against the committed openapi.json/api.ts; wiring this into the existing per-service workflows (.github/workflows/backend-ci.yml, frontend-ci.yml) requires cross-service artifact or job orchestration, not a one-line lint.
  2. The diff must be actionable. A raw JSON diff is noisy; the gate should fail with a clear "run pnpm generate:api" message and ideally show which paths/types changed, or contributors will disable it.
  3. Committed-vs-generated policy is a decision. Either openapi.json/api.ts stay committed (and the gate enforces freshness) or they become generated artifacts (and build steps change); each has different implications for CI caching and local dev.

Proposed design

Add a CI step that exports the backend spec and runs the frontend's generate:api, then fails if the committed files differ. Prefer the backend exporting to a known path in CI and the frontend job comparing. Document the two-step contract in the contributing guide.

Acceptance criteria

CI

  • A PR that changes a backend DTO/route without updating app/frontend/openapi.json (and src/lib/generated/api.ts) fails CI.
  • A PR that regenerates the spec passes, and the failure message tells the contributor the exact command to run.

Docs

  • The contributing guide documents how to keep the OpenAPI snapshot in sync across backend and frontend.

Out of scope

JWT token wiring in api-client.ts and frontend integration tests for the generated client are separate issues.

Getting started

Files: app/frontend/openapi.json, app/frontend/src/lib/generated/api.ts, app/frontend/package.json (generate:api), app/backend/package.json (spec:export), .github/workflows/frontend-ci.yml, .github/workflows/backend-ci.yml.

cd app/backend && npm run spec:export
cd app/frontend && pnpm generate:api
git diff --stat app/frontend/openapi.json app/frontend/src/lib/generated/api.ts

Good first files to read: app/frontend/package.json (the generate:api script) and .github/workflows/frontend-ci.yml to see where the gate would run.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaignarea:frontendFrontend (Next.js) areahighHigh severity issueskind:ciCI / CD

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions