How to Build an API-First Front End with OpenAPI, Orval, TanStack Query, Zod

javatuts1 pts0 comments

How to Build an API-First Frontend with OpenAPI, Orval, TanStack Query, Zod, and Next.js

The React Systems Newsletter

SubscribeSign in

How to Build an API-First Frontend with OpenAPI, Orval, TanStack Query, Zod, and Next.js

The React Systems Newsletter<br>May 27, 2026

Share

Frontend development has a repetitive problem that almost every team eventually rediscovers.<br>You receive an API endpoint.<br>Thanks for reading! Subscribe for free to receive new posts and support my work.

Subscribe

You write:<br>request functions

response types

React hooks

loading states

mutation handlers

mocks

validation logic

Then the backend changes one field.<br>Now:<br>generated screenshots fail

UI forms break

stale interfaces lie to TypeScript

QA opens twelve tickets

The larger the application becomes, the worse this cycle gets.<br>The irony is that modern teams already possess something capable of solving a large part of this problem:<br>the API contract.<br>That contract usually exists as an OpenAPI specification.<br>Unfortunately, many teams still treat OpenAPI as documentation.<br>Useful documentation.<br>Ignored documentation.<br>Static documentation.<br>But OpenAPI can be much more than a Swagger page living somewhere inside infrastructure documentation.<br>In a modern TypeScript workflow, OpenAPI can become the single source of truth powering your entire frontend integration layer.<br>Why API-First Development Actually Matters

Many frontend teams still work in a backend-last model.<br>The workflow usually looks like this:<br>UI design begins.

Backend implementation begins.

Frontend waits.

Backend finishes endpoints.

Frontend starts integration.

Everybody discovers mismatched assumptions.

You have probably lived through this already.<br>Examples:<br>Backend:<br>"full_name": "Jane Doe"<br>}Frontend expected:<br>fullName: string<br>}Backend:<br>"status": "pending_review"<br>}Frontend enum:<br>type Status = "draft" | "published";Runtime chaos follows.<br>API-First changes the order.<br>Instead of backend implementation being the first deliverable, the API contract becomes the first deliverable .<br>That means frontend developers can begin work before the backend exists.<br>Not with fake hand-written mocks.<br>Not with guessed interfaces.<br>With a real contract.

OpenAPI as a Development Asset — Not Documentation

An OpenAPI specification can drive far more than Swagger UI.<br>A modern workflow can automatically generate:<br>TypeScript models

API clients

React hooks

query utilities

mocks

validation schemas

documentation

testing helpers

The important shift is conceptual.<br>Treat OpenAPI like source code.<br>Version it.<br>Review it.<br>Diff it.<br>Generate from it.<br>Your repository structure might look like this:<br>my-app/<br>├── openapi/<br>│ └── schema.yaml<br>├── src/<br>│ ├── api/<br>│ ├── components/<br>│ ├── hooks/<br>│ └── app/<br>├── orval.config.ts<br>└── package.jsonKeeping specs inside Git matters.<br>You gain:<br>version history

review visibility

contract diffs

CI automation

team synchronization

Frontend engineers no longer rely on outdated screenshots of Swagger pages.

Generating a Typed Client with Orval

Instead of manually writing fetch wrappers, we will use Orval .<br>Install dependencies:<br>pnpm add -D orvalCreate configuration.<br>orval.config.ts<br>import { defineConfig } from "orval";

export default defineConfig({<br>catalogApi: {<br>input: {<br>target:<br>"./openapi/schema.yaml"<br>},

output: {<br>target:<br>"./src/api/generated.ts",

client:<br>"react-query",

mode:<br>"single"<br>});Now add a script.<br>"scripts": {<br>"api:generate":<br>"orval"<br>}Run generation.<br>pnpm api:generateOrval now generates:<br>request functions

types

TanStack Query hooks

mutation helpers

No manual API boilerplate.

Building a Real Query Layer with TanStack Query

Generated clients become significantly more useful once combined with TanStack Query.<br>Install:<br>pnpm add @tanstack/react-queryCreate provider setup.<br>providers/query-provider.tsx<br>"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

export function AppProviders({ children }: React.PropsWithChildren) {

return (

{children}

);<br>}Now generated hooks become extremely clean.<br>const { data, isLoading, error } = useGetProducts();No handwritten hooks.<br>No duplicated loading logic.<br>No copy-pasted fetch abstractions.

Type Safety Is Useful — But Runtime Validation Still Matters

This is where many TypeScript projects become overconfident.<br>Generated interfaces help.<br>They do not validate runtime payloads.<br>Your server can still return:<br>"price": "broken"<br>}while TypeScript confidently believes:<br>price: numberThis is where Zod enters the workflow.<br>Install:<br>pnpm add zodDefine schemas.<br>import { z }<br>from "zod";

export const ProductSchema =<br>z.object({<br>id: z.number(),<br>title: z.string(),<br>price: z.number()<br>});Runtime validation:<br>const result =<br>ProductSchema.parse(<br>response.data<br>);Now bad payloads fail loudly.<br>Not silently.<br>API-First becomes stronger when combined with runtime guarantees.

Starting Frontend Development Before Backend Exists

One of the most valuable parts of...

openapi frontend query orval backend first

Related Articles