Open API3から、react-query, zod, mswを生成する
Orvalを利用してコードを生成していきます。
Generate, valid, cache and mock in your frontend applications all with your OpenAPI specification.
OpenAPI v3を利用して、client libraryだけじゃなく、msw、zodやtanstack-query,swrなどを生成してくれるgeneratorです。
Typespecについては、discussion中となっております。
0. before do
想定しているディレクトリ構成とファイルの中身。
tree . -I node_modules
.
├── README.md
├── compose.yaml
├── dockerfile
├── openapi3
│ ├── generated
│ │ └── @typespec
│ │ └── openapi3
│ │ └── openapi.yaml
├── package-lock.json
├── package.json
compose.yaml
compose.yaml
services:
http_schema_container:
container_name: http_schema_container
build:
context: .
dockerfile: dockerfile
tty: true
restart: always
volumes:
- .:/usr/src/app
dockerfile
FROM node:23-alpine3.19
WORKDIR /usr/src/app
openapi3/generated/@typespec/openapi3/openapi.yaml
openapi3/generated/@typespec/openapi3/openapi.yaml
openapi: 3.0.0
info:
title: (title)
version: 0.0.0
tags: []
paths:
/pets:
get:
operationId: Pets_list
parameters:
- name: filter
in: query
required: true
schema:
type: string
explode: false
responses:
'200':
description: The request has succeeded.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
post:
operationId: Pets_create
parameters: []
responses:
'200':
description: The request has succeeded.
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
/pets/{id}:
get:
operationId: Pets_read
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: The request has succeeded.
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
/stores:
get:
operationId: Stores_list
parameters:
- name: filter
in: query
required: true
schema:
type: string
explode: false
responses:
'200':
description: The request has succeeded.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Store'
/stores/{id}:
get:
operationId: Stores_read
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: The request has succeeded.
content:
application/json:
schema:
$ref: '#/components/schemas/Store'
components:
schemas:
Address:
type: object
required:
- street
- city
properties:
street:
type: string
city:
type: string
Pet:
type: object
required:
- name
- age
properties:
name:
type: string
age:
type: integer
format: int32
Store:
type: object
required:
- name
- address
properties:
name:
type: string
address:
$ref: '#/components/schemas/Address'
1. Orvalの依存関係とConfigファイルを生成する
% npm i orval -D
% touch orval.config.ts
orval.config.ts
import { defineConfig } from 'orval';
export default defineConfig({
petstore: {
input: './openapi3/generated/@typespec/openapi3/openapi.yaml',
output: './src/gen/petstore.ts',
hooks: {
afterAllFilesWrite: 'prettier --write',
},
},
});
package.json
"scripts": {
"gen:orval": "orval --config ./orval.config.ts",
}
% npm run gen:orval
% tree . -I node_modules
.
├── README.md
├── compose.yaml
├── dockerfile
├── models
│ └── store.tsp
├── openapi3
│ ├── generated
│ │ └── @typespec
│ │ └── openapi3
│ │ └── openapi.yaml
├── orval.config.ts
├── package-lock.json
├── package.json
└── src
└── gen
└── petstore.ts
src/gen/petstore.ts
/**
* Generated by orval v7.1.1 🍺
* Do not edit manually.
* (title)
* OpenAPI spec version: 0.0.0
*/
import axios from "axios";
import type { AxiosRequestConfig, AxiosResponse } from "axios";
export type StoresListParams = {
filter: string;
};
export type PetsListParams = {
filter: string;
};
export interface Store {
address: Address;
name: string;
}
export interface Pet {
age: number;
name: string;
}
export interface Address {
city: string;
street: string;
}
export const petsList = <TData = AxiosResponse<Pet[]>>(
params: PetsListParams,
options?: AxiosRequestConfig,
): Promise<TData> => {
return axios.get(`/pets`, {
...options,
params: { ...params, ...options?.params },
});
};
export const petsCreate = <TData = AxiosResponse<Pet>>(
pet: Pet,
options?: AxiosRequestConfig,
): Promise<TData> => {
return axios.post(`/pets`, pet, options);
};
export const petsRead = <TData = AxiosResponse<Pet>>(
id: string,
options?: AxiosRequestConfig,
): Promise<TData> => {
return axios.get(`/pets/${id}`, options);
};
export const storesList = <TData = AxiosResponse<Store[]>>(
params: StoresListParams,
options?: AxiosRequestConfig,
): Promise<TData> => {
return axios.get(`/stores`, {
...options,
params: { ...params, ...options?.params },
});
};
export const storesRead = <TData = AxiosResponse<Store>>(
id: string,
options?: AxiosRequestConfig,
): Promise<TData> => {
return axios.get(`/stores/${id}`, options);
};
export type PetsListResult = AxiosResponse<Pet[]>;
export type PetsCreateResult = AxiosResponse<Pet>;
export type PetsReadResult = AxiosResponse<Pet>;
export type StoresListResult = AxiosResponse<Store[]>;
export type StoresReadResult = AxiosResponse<Store>;
2. 利用するLibraryを設定していく
React Query、msw、zodの設定を追加してコードを生成する。
2-1. Zodの設定を追加する
Zodの設定を追加して、codeを生成していく。
orval.config.ts
import { defineConfig } from 'orval';
const OpenAPIFilePath = "./openapi3/generated/@typespec/openapi3/openapi.yaml"
const OutPutDir = "./src/gen"
export default defineConfig({
pestore: {
input: {
target: OpenAPIFilePath
},
output: `${OutPutDir}/petstore.ts`
},
petstoreZod: {
input: {
target: OpenAPIFilePath
},
output: {
client: "zod",
mode: 'tags-split',
target: `${OutPutDir}/zod`,
fileExtension: '.zod.ts',
}
}
});
% npm run gen:orval
% tree . -I node_modules
.
├── README.md
├── compose.yaml
├── dockerfile
├── models
│ └── store.tsp
├── openapi3
│ ├── generated
│ │ └── @typespec
│ │ └── openapi3
│ │ └── openapi.yaml
├── orval.config.ts
├── package-lock.json
├── package.json
└── src
└── gen
├── petstore.ts
└── zod
└── default
└── default.zod.ts
2-2. React QueryとMSWを生成する
React Queryとmswに設定する
orval.config.ts
import { defineConfig } from 'orval';
const OpenAPIFilePath = "./openapi3/generated/@typespec/openapi3/openapi.yaml"
const OutPutDir = "./src/gen"
export default defineConfig({
pestore: {
input: {
target: OpenAPIFilePath
},
output: {
mode: 'tags-split',
target: `${OutPutDir}/petstore.ts`,
schemas: `${OutPutDir}/model`,
client: 'react-query',
mock: true,
}
},
petstoreZod: {
input: {
target: OpenAPIFilePath
},
output: {
client: "zod",
mode: 'tags-split',
target: `${OutPutDir}/zod`,
fileExtension: '.zod.ts',
}
}
});
% npm run gen:orval
% tree . -I node_modules
.
├── README.md
├── compose.yaml
├── dockerfile
├── models
│ └── store.tsp
├── openapi3
│ ├── generated
│ │ └── @typespec
│ │ └── openapi3
│ │ └── openapi.yaml
├── orval.config.ts
├── package-lock.json
├── package.json
└── src
└── gen
├── default
│ ├── default.msw.ts
│ └── default.ts
├── model
│ ├── address.ts
│ ├── index.ts
│ ├── pet.ts
│ ├── petsListParams.ts
│ ├── store.ts
│ └── storesListParams.ts
├── petstore.ts
└── zod
└── default
└── default.zod.ts