← All writing
React apps / TypeScript / OpenAPI

Keeping React Applications Close to Their API Contracts

Generated clients, server-state management and shared UI boundaries across Culina, Elysion and Muninn.

· 2 min read

Separate the contract from the screen

Culina and Elysion use OpenAPI-generated TypeScript clients for their React frontends. The generated code connects the application to the backend’s request and response shapes. TanStack Query handles server state and caching; TanStack Router handles navigation.

Those tools have different responsibilities. A generated client describes how to call an endpoint. Server-state management concerns the lifetime of fetched data. Routing describes how a user reaches a view. Keeping those roles visible helps avoid making every component responsible for the full integration.

Generated types also have limits. They do not decide when a screen should refresh its data, how an error should be presented or what a permission means in a workflow. The application still needs to make those choices explicitly.

Forms are another boundary

React Hook Form and Zod support forms in Culina and Elysion, with MUI providing interface components and i18next supporting localization. Input being editable in a browser does not make it valid for every backend use case; form behavior and the API contract need to stay aligned.

Recipe creation illustrates why the domain matters. Ingredient groups and ordered preparation steps are not just strings to place in generic inputs. The interface must preserve the relationships the backend models, while presenting a usable editing experience.

Share what has a real second consumer

Elysion has separate tenant and administration applications in an Nx and pnpm workspace. Shared libraries cover forms, themes, authentication helpers, error handling and API access. Both applications provide actual consumers for those shared capabilities.

Dependency rules keep application-specific code separate from shared libraries. That matters because a reusable form component and a tenant-specific workflow have different reasons to change. Moving everything into a shared directory would blur the boundary the workspace is intended to maintain.

Product constraints still shape the frontend

Muninn also uses React, TanStack Router and TanStack Query, with generated clients wrapped behind application-specific APIs. Its browser encryption, local search and IndexedDB storage add responsibilities that do not disappear into a networking library.

The shared lesson across these products is to let generated code own the contract and give product behavior an explicit home. Storybook and frontend tests support the UI boundary; backend and integration tests support the API boundary. Reuse works best when those boundaries remain understandable to the next person changing a screen.