Full-stack TypeScript app
Generate types, validators, query hooks, mocks, and forms from one OpenAPI document — the standard SKMTC combination for a TypeScript/React app.
This recipe exists for one moment: add a field to the schema, regenerate, and watch the type, the validator, and the form update together while everything that imports them stays consistent. The last section stages that moment; the rest builds the stack that makes it possible.
What you'll build
A src/generated/ tree containing:
- TypeScript types matching every schema component
- Zod validators for those same schemas
- Tanstack Query hooks (
useQuery/useMutation) for every operation - MSW handlers for development-time mocking
- shadcn/ui form components for mutating operations with object bodies
All five generators converge on shared definitions — the engine produces each schema exactly once, regardless of how many generators need it.
Stack
- Vite or Next.js (your existing app, no SKMTC-runtime needed)
- React + Tanstack Query + shadcn/ui
- MSW (for dev-only mocks)
Generators used
Types: @skmtc/gen-typescript
Produces export type Pet = {...} per schema component. The
foundation everything else references.
Validators: @skmtc/gen-zod
Produces export const pet = z.object({...}) per schema component.
Used at runtime boundaries (API responses, form submissions).
Query hooks: @skmtc/gen-tanstack-query-fetch-zod
Produces useQuery for GET/DELETE and useMutation for
POST/PUT/PATCH (when there's a request body). Hooks call fetch
directly — typical first edit when cloned is replacing fetch
with a team-specific wrapper.
Mocks: @skmtc/gen-msw
Produces per-operation http.get/http.post handlers plus a shared
toRoutesList(deps) factory. Wired into the app's MSW worker for
dev-mode mocking.
Forms: @skmtc/gen-shadcn-form
Produces a React form for every POST/PUT/PATCH operation with an object body. The form references both the Zod schema (for validation) and the mutation hook (for submit) — both already generated by the upstream generators.
Setup
skmtc init myapp src/generated
skmtc install @skmtc/gen-typescript myapp
skmtc install @skmtc/gen-zod myapp
skmtc install @skmtc/gen-tanstack-query-fetch-zod myapp
skmtc install @skmtc/gen-msw myapp
skmtc install @skmtc/gen-shadcn-form myappEdit .skmtc/myapp/.settings/client.json:
{
"source": "https://api.example.com/openapi.json",
"settings": {
"basePath": "src/generated"
}
}Generate:
skmtc generate myappStep-by-step
What ends up where in src/generated/ (each generator's
toExportPath claims its own subtree):
src/generated/
├── types/
│ ├── pet.generated.ts # type + Zod schema (both generators converge on this file)
│ ├── order.generated.ts # ditto
│ └── ...
├── services/
│ ├── useGetPetById.generated.ts # query hook
│ ├── useAddPet.generated.ts # mutation hook
│ └── ...
├── forms/
│ ├── AddPet.generated.tsx # wired React form
│ └── ...
└── mocks/
└── handlers.generated.ts # all MSW handlers + toRoutesList factoryThe hooks file imports from the types/Zod file:
// src/generated/services/useGetPetById.generated.ts
import { pet, type Pet } from '../types/pet.generated.ts'
import { useQuery } from '@tanstack/react-query'
export const useGetPetById = (args: { petId: number }) => useQuery({...})The forms reference the hook and the Zod schema:
// src/generated/forms/AddPet.generated.tsx
import { pet } from '../types/pet.generated.ts'
import { useAddPet } from '../services/useAddPet.generated.ts'
// ...Everything composes because all five generators share the
(name, exportPath) cache. See cross-generator coordination
concept.
Result
In your app:
import { useGetPetById } from '@/generated/services/useGetPetById.generated.ts'
import { AddPetForm } from '@/generated/forms/AddPet.generated.tsx'
export function PetDetailPage({ id }: { id: number }) {
const { data } = useGetPetById({ petId: id })
return (
<div>
<h1>{data?.name}</h1>
<AddPetForm />
</div>
)
}Set up MSW in development:
// src/setupMsw.ts
import { setupWorker } from 'msw/browser'
import { toRoutesList } from '@/generated/mocks/handlers.generated.ts'
const worker = setupWorker(...toRoutesList({ store: yourMockStore }))Variations
- Swap fetch transport. Clone
gen-tanstack-query-fetch-zodto use your team'sapiFetchwrapper. See how to swap a peer dependency. - Supabase backend. Use
@skmtc/gen-tanstack-query-supabase-zodinstead of the fetch variant. Addgen-shadcn-selectandgen-shadcn-tablefor the search/list UI components that pair with it. - Add a table. Install
@skmtc/gen-shadcn-tablefor list-GET operations.
The payoff: change the schema
Add one field to a schema component in your OpenAPI document — say
nickname: { type: string } on Pet — and regenerate:
skmtc generate <project>
git diff --stat src/generated/The diff touches every file that spells out Pet's shape: the type
gains a field, the validator gains a rule, the form gains an input.
The hooks and mocks that import those definitions stay consistent
without changing, because they reference the shared artifacts rather
than duplicating them (see
cross-generator coordination).
One schema edit, one regenerate, a fully consistent stack — this
property is what the recipe exists to demonstrate.
Source
This recipe's stack is the most common SKMTC usage pattern. Real
projects typically clone one or two of the generators (the
fetch wrapper, the form's submit-flow) to match team conventions.
The schema-level generators (gen-typescript, gen-zod,
gen-msw) usually run unmodified.
See also
- Tutorial 02: Multiple generators — the entry-point walkthrough for this combination
- Cross-generator coordination concept
- Stock generators overview
API mocks for frontend development
Drive frontend development against MSW mocks generated from your backend's OpenAPI schema — schema-first, no backend required.
Multi-project monorepo
One repo, multiple SKMTC projects writing into separate output directories — useful when you have several APIs or multiple consumer apps with different generator combinations.