SWAGGER / OPENAPI → TANSTACK QUERY
Your spec in. Typed hooks out.
Point it at a Swagger spec, run one command, get fully-typed TanStack Query code — one folder per controller, using your own axios instance.
npm i -D swagger-to-tanstack-query- Swagger 2.0
- OpenAPI 3.x
- TanStack Query v5
- Bring your own axios
- MIT
An OpenAPI getContact operation from swagger.json transforming into a typed getContact query function in contact/apis.ts.
Everything typed, nothing hand-written.
Controller-based output
One folder per OpenAPI tag — each self-contained with its own types, apis, queries and mutations.
TanStack Query v5
GET / HEAD become queryOptions; POST / PUT / PATCH / DELETE become useXxx mutation hooks.
Bring your own axios
baseURL, auth and interceptors stay in your instance. Generated code just imports it.
Response envelope unwrapping
Return the inner payload, not { data, message, … } — type-safe through a generic envelope.
Typed errors
Every hook's error is typed as AxiosError<YourErrorType>, applied per hook.
Faithful types
$ref, allOf / oneOf / anyOf, enums, nullable, arrays, maps and binary → Blob.
Header params & uploads
in: header params via axios config; multipart/form-data assembled as FormData.
Docs preserved
summary and @deprecated from the spec carry through as JSDoc on the generated code.
Swagger 2.0 & OpenAPI 3.x
Both spec dialects are parsed and normalized to the same typed output.
Safe identifiers
Reserved words and wire-name mismatches (page-size, delete) are handled correctly.
From spec to hooks in three steps.
- 01
Add a config file
Point swagger-to-tanstack-query.config.json at your spec and your axios instance.
swagger-to-tanstack-query.config.json json { "url": "https://api.example.com/v3/api-docs", "output": "./src/api", "client": { "path": "@/lib/axios", "name": "axiosInstance" }, "response": { "dataField": "data", "envelope": { "path": "@/lib/axios", "name": "CommonResponse" } }, "error": { "path": "@/lib/axios", "name": "ApiError" } } - 02
Run one command
Generate the full typed client — one folder per controller.
terminal bash $ npm run codegen swagger-to-tanstack-query spec : https://api.example.com/v3/api-docs output : ./src/api client : axiosInstance from "@/lib/axios" generating... done. 13 controllers, 65 files. - 03
Use the hooks
Drop the generated queryOptions straight into useQuery.
ContactName.tsx tsx import { useQuery } from "@tanstack/react-query"; import { contactQueries } from "@/api/contact"; const { data } = useQuery( contactQueries.getContact({ contactId: 1 }), ); // ^? Detail | undefined
One folder per controller.
The real generated output for a contact controller — types, api functions, queryOptions, mutation hooks and a barrel.
// contact/types.ts
/** Common API response envelope */
export interface Detail {
id?: number;
name: string;
status?: "ACTIVE" | "ARCHIVED" | "DELETED";
tags?: Array<Tag>;
}// contact/apis.ts
import { axiosInstance as client } from "@/lib/axios";
import type { CommonResponse } from "@/lib/axios";
import type { Detail, Create } from "./types";
/** Get contact details */
export const getContact = ({ contactId }: { contactId: number }) =>
client.get<CommonResponse<Detail>>(`/api/v1/contacts/${contactId}`).then((res) => res.data.data);
/** Create a contact */
export const createContact = ({ body }: { body: Create }) =>
client.post<CommonResponse<Create>>(`/api/v1/contacts`, body).then((res) => res.data.data);// contact/queries.ts
import { queryOptions } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import type { ApiError } from "@/lib/axios";
import * as apis from "./apis";
export const contactQueries = {
getContact: (args: { contactId: number }) =>
queryOptions<Awaited<ReturnType<typeof apis.getContact>>, AxiosError<ApiError>>({
queryKey: ["contact", "getContact", args],
queryFn: () => apis.getContact(args),
}),
};// contact/mutations.ts
import { useMutation } from "@tanstack/react-query";
import type { UseMutationOptions } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import type { ApiError } from "@/lib/axios";
import * as apis from "./apis";
import type { Create } from "./types";
/** Create a contact */
export const useCreateContact = (
options?: Omit<
UseMutationOptions<Awaited<ReturnType<typeof apis.createContact>>, AxiosError<ApiError>, { body: Create }>,
"mutationFn"
>,
) =>
useMutation({
mutationFn: (vars: { body: Create }) => apis.createContact(vars),
...options,
});// contact/index.ts
export * from "./types";
export * from "./apis";
export * from "./queries";
export * from "./mutations";