目次

  1. OpenAPIでドキュメントを書くメリットなど
  2. OpenAPI GeneratorでClient情報を吐き出す
  3. Vue, Nuxtで利用する
  4. APIの変更に追従しやすく、型もあるし幸せ

OpenAPIでドキュメントを書くメリットなど

弊社では以下のブログでも書いているように、API仕様のドキュメント化にOpenAPIを活用しています。

フロントエンドとバックエンドで実装する人が異なる場合、また、将来的に異なる人が実装する可能性がある場合や、単純に見やすいドキュメント作りという点で考えても、OpenAPIを利用するメリットは大きいと感じております。
そして、そのメリットを拡大してくれるなと感じているのが、OpenAPI Generatorを利用した開発です。
弊社ではフロントエンドの実装はNuxt.js + TypeScriptを利用することが多いのですが、そこでaxiosなどを利用してAPIをリクエストする部分などにOpenAPI Generatorで吐き出した型を利用すると、

  • 型に関するファイルを自分で書かなくて済む
  • 自分で書かないので間違いが発生しにくい(元々のファイルが間違っていたらダメなんですが)
  • APIの修正に追従しやすい

などのメリットが増えました。
今回はOpenAPI GeneratorのVue.js, Nuxt.jsへの導入方法などをご紹介したいと思います。


1. OpenAPI GeneratorでClient情報を吐き出す

1.1 packageのインストール

yarnやbrewでglobalにinstallする方法もあるのですが、CIなどでチェックする場合にも使いたいので、私はyarnでprojectごとにopenapi-generatorをinstallしています。

$ yarn add -D @openapitools/openapi-generator-cli

or

$ npm install @openapitools/openapi-generator-cli -D

1.2 package.jsonのscriptsに追記

Clientを生成するscript generate-client などを追記します。

openapi-generator generate -g typescript-axios

-g で利用するGeneratorを選択します。
今回は typescript-axios を利用していますが、利用する言語・フレームワークによって変更可能です。(参考 *2)

<小ネタ>
check-yml format-yml validate-schema なども合わせて記述していますが、
prettierを利用したyamlのフォーマットチェックやgeneratorのvalidateはCIでチェックする際に便利なのでおすすめです😎

{
  "name": "Application-Name",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "check-yml": "prettier --check './path/to/openapi.yaml'",
    "format-yml": "prettier --write './path/to/openapi.yaml'",
    "validate-schema": "openapi-generator validate -i ./path/to/openapi.yaml",
    "generate-client": "openapi-generator generate -g typescript-axios -i ./path/to/openapi.yaml -o ./frontoend-application-directory/src/types/typescript-axios",
  },
  "devDependencies": {
    "@openapitools/openapi-generator-cli": "^1.0.10-4.2.3",
  }
}

1.3 Clientの生成

先ほど登録したscriptでclientを生成します。

$ yarn generate-client

生成後のソースは以下のようになります。
これでClient生成までは完了で準備が終わった状態です。

$  tree -L 2 ./types
./types
├── client-axios
│   ├── api.ts
│   ├── base.ts
│   ├── configuration.ts
│   ├── git_push.sh
│   └── index.ts
└── index.d.ts

2. Vue, Nuxtで利用する

ここからは先ほど生成したClientをどのように利用しているかの話しです。
生成された型情報などを、どのように使っていくのかをログイン画面のソースをベースに説明します。

2.1 Yamlと生成されたclient

以下は api.ts に含まれる ログイン時に必要な loginId, passwordの情報です。
もともとのOpenAPIの情報が記述されたyamlの内容をgenerateすると api.ts の内容が生成されます。

openapi.yaml
からLoginRequest部分を抜粋。

    LoginRequest:
      title: LoginRequest
      type: object
      description: 認証情報
      required: [loginId, password]
      properties:
        loginId:
          type: string
          description: ログインID
        password:
          type: string
          description: パスワード

ファイル: ./types/typescript-client/api.ts

/**
 * 認証情報
 * @export
 * @interface LoginRequest
 */
export interface LoginRequest {
    /**
     * ログインID
     * @type {string}
     * @memberof Auth
     */
    loginId: string;
    /**
     * パスワード
     * @type {string}
     * @memberof Auth
     */
    password: string;
}

2.2 生成されたClientをVueから利用する

上で生成されたClient情報を、login.vueで読み込みます。
data の interfaceで生成された型の情報を利用しています。
普段だとこの情報を自分で記述する必要があると思いますが、
生成された内容を利用するだけになるので楽ができますね🤗

ファイル: ./pages/login.vue

import Vue from 'vue'
import Cookies from 'universal-cookie'
import { LoginRequest } from '@/types/typescript-axios'

interface Data {
  value: LoginRequest,
}

export default Vue.extend({
  components: {
  },
  data: (): Data => {
    return {
      value: {
        loginId: '',
        password: '',
      },
    }
  },
  methods: {
    async login() {
      try {
        await this.$store.dispatch('auth/login', {
          basePath: process.env.VUE_APP_BASE_PATH, // APIのURL
          value: this.value,
          cookie: new Cookies(),
          stage: process.env.VUE_APP_STAGE, // localでcookieにSecure属性をつけないようするために渡しているだけです(不要な方は消してOK)
        })
      } catch (error) {
        // エラーをよしなに
      }
    },
  },
})

2.3 生成されたClientをstore(Vuex)で利用する

login.vue の loginメソッドで呼ばれているstore/authの内容です。
こちらでは、 LoginRequest の型情報に加えて、 AuthApi のclassもimportします。
storeの中でこれを利用してAPIへのリクエストを行っています。

生成された AuthApi の方でaxiosを利用してリクエストする部分が記述してあるため、storeではエンドポイントに対応したメソッドを呼んでやるだけになります(便利ポイントですね!)

AuthApi({ basePath: params.basePath })

と、APIのURLを渡していますが、認証を求められるエンドポイントなどは以下のようにtokenを渡してやると、リクエスト時に Bearer など指定の認証方法に応じてHeaderに追加してくれます。

new MeApi({
basePath: params.basePath,
accessToken: params.accessToken,
})

ファイル: ./store/auth.ts

import { Module, ActionTree, GetterTree, MutationTree } from 'vuex'
import { LoginRequest, AuthApi } from '@/types/typescript-axios'
import Cookies, { CookieSetOptions } from 'universal-cookie'
import { RootState } from '@/types/index'

interface AuthState {
}

interface LoginParams {
  basePath?: string,
  value: LoginRequest,
  cookie: Cookies,
  stage: string,
}

export const state = (): AuthState => ({
})

export const getters: GetterTree<AuthState, RootState> = {
}

export const mutations: MutationTree<AuthState> = {
}

export const actions: ActionTree<AuthState, RootState> = {
  async login(_ctx, params: LoginParams) {
    try {
      const { data } = await(new AuthApi({ basePath: params.basePath }))
        .postAuthLogin(params.value)
      params.cookie.set(
        'accessToken',
        data.accessToken,
        {
          secure: params.stage !== 'local',
          maxAge: data.expiresIn,
        } as CookieSetOptions,
      )
    } catch (error) {
      throw error.response
    }
  },
}

export const auth: Module<AuthState, RootState> = {
  namespaced: true,
  getters,
  mutations,
  actions,
}

3. APIの変更に追従しやすく、型もあるし幸せ

上記のようにOpenAPIから型の情報を生成し、フロントエンドで利用する流れを実装してみました。

APIの変更を把握し追従するのは、フロントエンドの組み込みをする上でなかなか骨の折れる部分かもしれませんが、Generatorを利用することでビルドエラーなどで修正するポイントが把握できるので事故りにくい状態が作れるかと思います。

あとは、フロントエンドのテストにうまく組み込みたいなと思っているところですが、そこがまだ出来ていないので、今後の課題とさせていただきます。


参考

*1 https://openapi-generator.tech/
*2 OpenAPI generatorを試してみる
*3 https://github.com/OpenAPITools/openapi-generator