1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Orvalを利用して、OpenAPI v3からtanstack-library, client-library, zod, mswを自動生成する

Last updated at Posted at 2024-10-21

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 Querymswzodの設定を追加してコードを生成する。

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
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?