- Next.js frontend (App Router) + Express backend (TypeScript)
express-oas-generatorobserves API traffic and writesopenapi.yaml- Orval generates a typed client + TanStack Query hooks from
openapi.yaml - Next.js rewrites
/api/*to the backend, so the frontend calls your backend without CORS
auto-tanstackquery-hook
├── app
│ ├── layout.tsx
│ ├── page.tsx
│ ├── providers.tsx
│ └── globals.css
├── backend
│ ├── server.ts
│ └── package.json
├── public
│ ├── frontend.png
│ └── swagger.png
├── src
│ └── api
│ ├── todoApi.ts
│ └── todoApi.schemas.ts
├── openapi.yaml
├── orval.config.ts
├── next.config.ts
└── README.md
- Strong types: request/response types come from the OpenAPI spec.
- Fewer bugs: compile-time checks for params/bodies.
- Less boilerplate: hooks, keys, and clients scaffolded for you.
- Consistency: every endpoint follows the same patterns.
- Faster iteration: change spec → regenerate → use.
- Autocomplete: endpoints, params, and return types.
- Tanstack Query integration: caching, refetch, errors included.
- Centralized changes: update spec once for all consumers.
- Easier onboarding: predictable, discoverable API surface.
- Scale-ready: large APIs stay manageable as they grow.
backend/Express server (tsx server.ts)openapi.yamlOpenAPI spec (auto-written by backend after requests)orval.config.tsOrval configurationsrc/api/Generated client and hooks (todoApi.ts,todoApi.schemas.ts)app/Next.js app (App Router)
- Node 18+ (Node 22 used here)
npm install
cd backend && npm install && cd ..- Backend (port 4000):
cd backend
nd # alias for: npm run dev- Frontend (port 3000):
nd # alias for: npm run dev- Start backend (port 4000) and frontend.
- Exercise endpoints (via UI or script) so
express-oas-generatorwrites/updatesopenapi.yaml.- Example endpoints included:
- Users (optional):
/api/users(GET/POST) [can be commented] - Todos (demo):
/api/todos(GET/POST),/api/todos/:id(DELETE)
- Users (optional):
- Example endpoints included:
- Regenerate client & hooks from the spec:
npx orval --config orval.config.ts- Use the generated hooks in the frontend:
import { useGetApiTodos, usePostApiTodos, useDeleteApiTodosId } from "@/src/api/todoApi";- Start backend:
cd backend && nd - Start frontend:
nd - Regenerate hooks:
npx orval --config orval.config.ts - Optional: Add a script to auto-hit endpoints for spec updates (see
backend/scripts/record-spec.tsidea).
next.config.ts:
export default {
async rewrites() {
return [{ source: "/api/:path*", destination: "http://localhost:4000/api/:path*" }];
},
};openapi.yamlupdates only after the backend sees real requests for new routes.- Run Orval after spec changes.
- Generated code lives in
src/api/; do not edit it manually.
- With the backend running, open
http://localhost:4000/api-docsto view interactive Swagger UI. - The spec (
openapi.yaml) is generated/updated after endpoints are hit.
- The backend writes
openapi.yamlautomatically after you exercise endpoints at runtime. - If you delete
openapi.yamlandsrc/api/*, you can fully regenerate:- Start backend and hit endpoints (via UI or script) so the generator rewrites
openapi.yaml. - Regenerate client/hooks with Orval.
- Start backend and hit endpoints (via UI or script) so the generator rewrites
# Start backend (port 4000)
cd backend && npm run dev
# (In another terminal) Start frontend
npm run dev
# After hitting endpoints, (re)generate client/hooks
npx orval --config orval.config.ts
# Optional: watch mode
npx orval --config orval.config.ts --watch
