Skip to main content

kubb-react-query

kubb-react-query stands up a fully-typed React Query data layer from an OpenAPI/Swagger spec: a separate API server emits a vendored spec, the Kubb CLI generates types, zod schemas, a fetch client, and TanStack Query hooks into a dedicated packages/<svc>-api package, and the app consumes that API exclusively through the generated hooks. It exists to make a well-known but easy-to-drift pattern — codegen a data layer from a spec — repeatable and checkable across services, rather than something each package reinvents slightly differently.

What it does

Given an API server that owns an OpenAPI spec, the skill wires the full chain end to end:

apps/api ──(emit)──▶ packages/<svc>-api/openapi.json (vendored spec, checked in)

kubb generate (kubb.config.ts)

src/{types, zod, clients, hooks} ← imports the two SEAM packages

apps/web imports @scope/<svc>-api/hooks

useGetX() for lists · usePostX()/usePutX()/useDeleteX() for mutations

It offers two entry points: scaffold a brand-new packages/<svc>-api via the bundled bin/add-api-client.mjs generator, or review/repair an existing setup by walking a fixed checklist of nine invariants until it's compliant. Supporting material ships alongside the skill body: an annotated kubb.config.ts, a package manifest with the subpath exports map, a tsconfig.json, worked consumption examples, a CI drift-check recipe, and an ARCHITECTURE.md that narrates the rationale behind each invariant.

Why it was created

Documented provenance comes from the skill's own metadata rather than a dedicated founding dossier — no docs/sessions/ or docs/plans/ writeup narrates why this skill was built (this page's own "Why it was created" section is the closest thing), so there's no separate founding narrative beyond what the skill itself records.

That record is specific, though. The metadata's abstract frames the skill as encoding "9 invariants — vendored spec, one package per service, two hand-written seam packages (fetch-client + react-query), tag grouping, name transformers, an idempotent post-generate codemod, disciplined hook consumption, and a CI drift check — plus reference templates, seam implementations (@cogs/fetch-client, @cogs/react-query), and a bin/add-api-client.mjs scaffolder." The git history corroborates this: the skill was added in commit 6a85750 ("feat(skills): add kubb-react-query skill") with the message "Relocated from next-starters — the skill documents @cogs/fetch-client and @cogs/react-query, which live here, so it now sits next to its source of truth instead of a separate repo." In other words, the pattern predates this skill's write-up here — it was already in use via the next-starters scaffolding tool — and the skill was moved into this repo specifically because the two seam packages it prescribes are implemented here. A follow-up commit, 644d780 ("chore(skills): enable all 5 skills for npm publish"), made it publishable after validation, alongside four sibling skills.

ARCHITECTURE.md adds one more provenance detail worth surfacing: the overall pattern is "lifted from the open-source environment-manager-ui (packages/*-api, 14 services on this exact shape) and the studio-os / offerly / work-loom monorepos," targeting Kubb v4, TanStack Query v5, and zod v3/4.

How it works

The skill's core claim is that generated code is disposable, but two hand-written "seam" packages are not — get those right and every service package looks identical. The seams are:

  • @scope/fetch-client — owns the base URL, auth token, per-request headers, timeout, and error shape. Kubb is pointed at it through pluginClient.importPath and pluginReactQuery.client.importPath.
  • @scope/react-query — re-exports all of @tanstack/react-query plus a shared createQueryClient, wired via pluginReactQuery's query.importPath and mutation.importPath.

Generated code must never import fetch or @tanstack/react-query directly — that's the tell that the seam has been bypassed. Ready-made implementations (@cogs/fetch-client, @cogs/react-query) exist so teams don't have to hand-roll them per service.

Beyond the seams, the remaining invariants close off the specific ways this pattern tends to rot in practice:

  • The spec must be vendored, not fetched live (input.path in kubb.config.ts points at a checked-in openapi.json, never a URL) — a live-fetch input makes codegen depend on a running server and breaks CI.
  • One package per API service, never generated code inside apps/*, so N services stay uniform.
  • group: { type: 'tag' } on the ts/zod/hooks/mcp plugins, so Kubb emits per-controller files instead of one giant barrel that's painful to diff.
  • transformers.name fixes domain acronyms/typos the spec leaks (e.g. clnCLN) so generated symbols read like the domain, not the raw schema.
  • A post-generate codemod, wired through Kubb v4's own hooks.done config, for fixups the config format can't express — must be idempotent so it's safe to run in a CI drift check. @cogs/kubb-transforms packages a ready-made version of this codemod.
  • App consumption discipline: components call useGetX for reads and usePost/Put/Patch/DeleteX for writes, invalidating via the exported getXQueryKey. A raw fetch(/axios call against the API from a component is the smell test for a broken seam.
  • Regeneration as a CI drift check: a script re-runs generate → typecheck and fails if git diff is non-empty, turning server/client spec drift into a red CI check instead of a production incident.

The skill also documents a short list of concrete red flags (a URL input.path, generated files imported with a relative path from apps/*, a from '@tanstack/react-query' import inside generated code, one 400-export hooks/index.ts, a generate script with no CI companion) to make the review pass mechanical rather than exploratory.

When to use it / when not to

Reach for this skill when you're generating a react-query hooks layer from a Swagger/OpenAPI spec, standing up a new packages/<svc>-api package, or auditing an existing Kubb setup for drift or invariant violations — the SKILL.md description explicitly triggers on phrases like "generate react query hooks from swagger/openapi," "set up kubb," "typed api client package," and "add an api-client package for <service>."

It's not the right tool if there's no OpenAPI/Swagger spec to codegen from, if the project isn't using TanStack Query, or if the goal is a one-off script rather than a durable, regenerable package — the whole design assumes recurring generate runs against a spec that keeps changing, with the seam packages absorbing everything that shouldn't be regenerated away.