OpenAPI Client Generation

The idea is small and its effect is not: instead of writing frontend functions that call the API, generate them from the API’s OpenAPI schema. The generated client carries the endpoints, parameters, and response types the backend actually declares.

This is the decision that makes the rest of the full-stack pattern worth using, and I would keep it before almost anything else in the stack.

The bug class it removes

A hand-written client is a second copy of the contract. The backend knows an endpoint returns an object with created_at; the frontend has been told this by a developer who read the documentation. Nothing connects the two, so when the field is renamed, the backend changes and the frontend does not.

That failure is silent. There is no error at build time and often none at runtime — just undefined rendering as a blank cell in a table nobody looked at closely. It surfaces days later as a bug report about missing data, and the cause is several commits back.

With generation there is no second copy. Rename the field, regenerate, and TypeScript fails at build time on every line consuming it. The whole class of frontend-backend contract drift becomes a compile error, which is the strongest available guarantee short of a monolith.

FastAPI makes this nearly free because it derives the schema from the Pydantic models the endpoints already validate against. The schema is not extra work; it is a by-product of validation. Django REST Framework achieves the same through drf-spectacular, as in the UCO portal.

What it requires

Generation must be part of the build. A stale generated client is worse than a hand-written one, because it is confidently wrong and looks authoritative. If regeneration is a manual step someone remembers, it will drift, and the guarantee evaporates precisely when it is needed. This is the mistake to avoid, and it is easy to make.

The schema has to be intentional. Generated output reflects what the backend declares, so a response typed as dict generates a client typed as any, and the type safety is a formality. Sloppy schemas produce sloppy clients silently.

A layer above it is still needed. The generated client handles transport and types, not authentication headers, retries, error presentation, or reshaping responses for the UI. Those belong in a thin wrapper — and keeping that wrapper thin is what keeps regeneration cheap.

See also: APIs, FastAPI, Pydantic, Vue 3, and Apollo for how GraphQL solves the same problem differently.