Overview of openapi-react-query
any and as.website·Indexed 34 days ago
https://openapi-ts.dev/A toolset for turning OpenAPI 3.0 & 3.1 schemas into static TypeScript types. The ecosystem includes a CLI for schema transformation, openapi-fetch for a type-safe fetch client, and openapi-react-query, a type-safe wrapper around @tanstack/react-query.
any and as.noUncheckedIndexedAccess in your tsconfig.json for better type safety.# Install runtime dependencies
npm i openapi-react-query openapi-fetch
# Install development dependencies
npm i -D openapi-typescript typescript
# Generate TypeScript types from your OpenAPI schema
npx openapi-typescript ./path/to/api/v1.yaml -o ./src/lib/api/v1.d.tsThe $api.useQuery method is a type-safe wrapper around the original TanStack Query useQuery hook. It ensures that the data and error returned are fully typed based on the OpenAPI schema.
Key characteristics:
[method, path, params] to ensure unique caching per request.import { $api } from "./api";
export const App = () => {
const { data, error, isLoading } = $api.useQuery(
"get",
"/users/{user_id}",
{
params: {
path: { user_id: 5 },
},
}
);
if (!data || isLoading) return "Loading...";
if (error) return `An error occured: ${error.message}`;
return <div>{data.firstname}</div>;
};module and moduleResolution in your tsconfig.json. It is also highly recommended to enable noUncheckedIndexedAccess for improved type safety.npm i -D openapi-typescript typescript// tsconfig.json
{
"compilerOptions": {
"module": "ESNext", // or "NodeNext"
"moduleResolution": "Bundler", // or "NodeNext"
"noUncheckedIndexedAccess": true
}
}Use transform() to modify OpenAPI nodes before they are converted to TypeScript, or postTransform() to modify them after conversion. This is useful for mapping non-standard OpenAPI formats (like date-time or binary) to specific TypeScript types (like Date or Blob).
The metadata parameter provides context:
metadata.path: A $ref URI string pointing to the current schema object.metadata.schema: The schema object being transformed (available only in postTransform).metadata.ctx: The GlobalContext object.const types = await openapiTS(mySchema, {
transform(schemaObject, metadata): string {
// Example: Map 'date-time' format to Date type
if ("format" in schemaObject && schemaObject.format === "date-time") {
return schemaObject.nullable ? "Date | null" : "Date";
}
// Example: Map 'binary' format to Blob type
if ("format" in schemaObject && schemaObject.format === "binary") {
return schemaObject.nullable ? "Blob | null" : "Blob";
}
},
});openapi-msw wrapper. This allows you to maintain the same level of type confidence in your mocks as you do with your API client (e.g., when using openapi-fetch).npm install openapi-mswopenapi-ts-router to wrap a Hono router. This provides compile-time type safety for routes and methods, and integrates with Zod or Valibot for runtime validation of request parameters and bodies.import { Hono } from 'hono';
import { createHonoOpenApiRouter } from 'openapi-ts-router';
import { zValidator } from 'validation-adapters/zod';
import * as z from 'zod';
import { paths } from './gen/v1';
export const router = new Hono();
export const openApiRouter = createHonoOpenApiRouter<paths>(router);
// GET /pet/{petId} with path validation
openApiRouter.get('/pet/{petId}', {
pathValidator: zValidator(z.object({ petId: z.number() })),
handler: (c) => {
const { petId } = c.req.valid('param');
return c.json({ name: 'Falko', photoUrls: [] });
}
});
// POST /pet with body validation
openApiRouter.post('/pet', {
bodyValidator: zValidator(z.object({ name: z.string(), photoUrls: z.array(z.string()) })),
handler: (c) => {
const { name, photoUrls } = c.req.valid('json');
return c.json({ name, photoUrls });
}
});TypeScript unions do not natively provide XOR behavior. To ensure TypeScript can correctly discriminate between types in a oneOf composition, avoid mixing oneOf with other properties (like a shared type enum) in the same object. Instead, use oneOf by itself and move shared properties into a separate schema that the individual options inherit via allOf.
This approach results in simpler generated types and allows TypeScript to use the type property as a discriminator.
# ✅ Recommended Pattern
Pet:
oneOf:
- $ref: "#/components/schemas/Cat"
- $ref: "#/components/schemas/Dog"
PetCommonProperties:
type: object
properties:
name:
type: string
Cat:
allOf:
- "$ref": "#/components/schemas/PetCommonProperties"
properties:
type:
type: string
enum: [cat]The $api.queryOptions method generates type-safe Query Options that can be used with any TanStack Query API that accepts options, such as useQuery, useQueries, usePrefetchQuery, and QueryClient.fetchQuery. This is particularly useful for using query APIs not explicitly wrapped by openapi-react-query.
The returned object includes a queryKey formatted as [method, path, params] and a queryFn pre-configured as a fetcher function, ensuring that data and error types are correctly inferred from the OpenAPI schema.
import { useQuery } from '@tanstack/react-query';
import { $api } from "./api";
export const App = () => {
const { data, error, isLoading } = useQuery(
$api.queryOptions("get", "/users/{user_id}", {
params: {
path: { user_id: 5 },
},
}),
);
if (!data || isLoading) return "Loading...";
if (error) return `An error occured: ${error.message}`;
return <div>{data.firstname}</div>;
};paths object must exactly match the schema URL (e.g., /user/{user_id}). Enabling the --path-params-as-types flag allows you to use dynamic string variables for lookups, which the CLI will automatically map to the corresponding schema path.import type { paths } from "./my-openapi-3-schema";
const url = `/user/${id}`;
// With --path-params-as-types, this automatically matches paths['/user/{user_id}']
type UserResponses = paths[url]["responses"];