Nuxt.js×TypeScriptで自社製バックエンドとmicroCMSバックエンドを型安全に併用するTips

# 概要
[前回の記事](https://qiita.com/mejileben/items/11f206a51861bb404e1a)で、TypeScriptで利用しているバックエンドのAPIを型定義して利用する方法を紹介しました。今回はその応用編です。

APIの型定義とは？aspidaとは？という方は前記事をご覧ください。

APIを型定義することで、APIから返ってくる値は本来any型になってしまうところを、APIのパスと戻り値の型を[aspida](https://github.com/aspidajs/aspida)で紐付けることで型安全にAPIを扱えます。

本記事では、最近流行のヘッドレスCMSである[microCMS](https://microcms.io/)と、自社製のバックエンドAPIを併用している場合に、両方型安全に扱う方法を説明します。microCMSを例に挙げますが、microCMS以外のヘッドレスCMSを活用する際にもご利用いただけるTipsです。

# microCMSについて
microCMSは、簡単に言えばデータを入稿すればそれをGET/POSTできるAPIが自動で作られるサービスです。

個人利用でも無料で10個のAPIまで作成できるので、興味を持った方は利用を始めてみると良いでしょう。

APIが提供されるのがポイントで、例えばFirebaseであればFirebaseのSDKを入れないとデータを取ったりできないのに対して、APIのためクライアントは任意のライブラリを選定できます。JavaScriptですとfetchでもaxiosでもkyでもいいですし、弊社のようにネイティブアプリをリリースしている場合でも導入コストはほぼゼロです。

# 課題
microCMSを導入する場合、入稿されるデータは様々な形式になるため、管理が必要でしょう。弊社の場合、SEO目的のブログコンテンツに始まり、ユーザー向けの通知文章なども入稿しています。
フロントエンドでは、それぞれのAPIがどういったパスで、どういった形式のデータを返してくるかを管理する必要があります。

また、microCMSの場合はAPI KEYがアクセスに必要なので、全体で共通のaxiosインスタンスを使う必要がありますが、既に既存で自社製のバックエンドAPIを利用しているので、別途別のaxiosインスタンスを用意する必要があり、オレオレ設計になってしまう危険性があります。

まとめると以下のとおりです。

* APIのパスと、入稿データの管理が必要
* 既存のバックエンドとは別のaxiosインスタンスが必要


# 解決策
これらの課題に対しても、前記事で紹介したaspidaを活用することで解消できます。

aspidaをnpm installのうえで、下記のように設定ファイルを記述します。

```javascript:~/plugins/apiClient.ts
import { Plugin } from '@nuxt/types'
import axiosClient from '@aspida/axios'
import axios from 'axios'
import api, { ApiInstance } from '@/apis/$api'
import microApi, { ApiInstance as MicroApiInstance } from '@/apisMicrocms/$api'

declare module 'vue/types/vue' {
  interface Vue {
    $api: ApiInstance
    $microcms: MicroApiInstance
  }
}

declare module '@nuxt/types/app' {
  interface NuxtAppOptions {
    $api: ApiInstance
    $microcms: MicroApiInstance
  }
}

declare module 'vuex/types/index' {
  interface Store<S> {
    $api: ApiInstance
    $microcms: MicroApiInstance
  }
}

const plugin: Plugin = function ({ $axios }, inject) {
  inject('api', api(axiosClient($axios)))
  inject(
    'microcms',
    microApi(
      axiosClient(axios, {
        baseURL: 'https://HOGEHOGE.microcms.io/api/v1',
        headers: { 'X-API-KEY': process.env.MICROCMS_GET_API_KEY },
      })
    )
  )
}
export default plugin
```

aspida自体は下記の2行でセットアップが完了しますが、Nuxtで使うためにinjectする必要と、Context等のオブジェクトに対して型を定義する必要があります。

```
import api, { ApiInstance } from '@/apis/$api'
api(axiosClient($axios)) // これでaspidaを利用できるインスタンスが生成できる
```

また、今回は複数バックエンドを利用するので、ルートディレクトリに`aspida.config.js`を置きます([Docs](https://github.com/aspidajs/aspida/tree/master/packages/aspida/docs/ja#%E5%9E%8B%E5%AE%9A%E7%BE%A9%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E7%BD%AE%E3%81%8F%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%82%92-apis-%E4%BB%A5%E5%A4%96%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B))。

```javascript:~/aspida.config.js
module.exports = [
  { input: 'apis' },
  { input: 'apisMicrocms' },
]
```

上記のセットアップが完了していれば、Vueファイルからは下記実装によってmicroCMSからデータを取ることができます。

▼APIの型定義

```typescript:~/apisMicrocms/general-article/index.ts
import { MicroCmsArticle, MicroCmsRequest } from '../type'
export interface Methods {
  get: {
    query: MicroCmsRequest
    resBody: { contents: MicroCmsArticle[] }
  }
}
```

```typescript:~/apisMicrocms/type.ts
export type MicroCmsArticle = {
  id: string
  // サムネ
  image_url: {
    url: string
  }
  createdAt: string
  updatedAt: string
} & MicroCmsArticleCntent

export type MicroCmsArticleCntent = {
  title: string
  description: string
  body: string
}

export type MicroCmsText = {
  text: string
}

export type MicroCmsRequest = {
  // fieldsのtypoを防ぐために文字列リテラル型にする。随時増やす
  fields?: 'id,title,description,image_url'
  limit?: number
  filters?: string
}
```

▼Vueファイル

```javascript:sample.vue
  async fetch () {
    this.articles = (await this.$microcms.general_article.$get({
      query: {
        fields: 'id,title,description,image_url',
        limit: 1000,
      },
    })).contents
  },
```

aspidaの基本的な利用方法は、予め設定ファイルで指定したディレクトリ（ここでは~/apisと~/apisMicrocms）以下に型定義ファイルをTypeScriptで書き、`npx aspida`を実行すると良いです。

僕はmicroCMSで書かれるデータの大半がブログ記事であることに着目し、`apisMicrocms/type.ts`に基本的なブログ返り値の型を定義しています。

GET時のクエリパラメータも型定義できます。microCMSで作成されるAPIは割と独自の検索クエリを仕様として持っており、たとえば`?fields=id,title,description,image_url`のようなクエリによって、戻ってくる値を絞り込むことができます。GraphQLに近い世界観ですね。

それらの検索パラメータも`MicroCmsRequest`としてまとめておきます。microCMSの検索クエリの形式はできるかぎり覚えたくないので、エディタの補完で効くのに越したことはないからです。

下記のように、VSCodeで型補完が効いていることも確認できます。以下の例では記事一覧が取得できていますね。Veturを併用することでtemplateタグ内でも補完を効かせることができました。

![スクリーンショット 2020-05-07 9.54.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156567/f8d543a8-8478-bcc0-7605-4f179577a7fc.png)


# 課題解決の確認

さて、以上の実装で冒頭に示した課題が解決できたかどうかを確かめてみましょう。

* APIのパスと、入稿データの管理が必要
* 既存のバックエンドとは別のaxiosインスタンスが必要

APIのパスと入稿データの管理はaspidaに設定したことで終わっており、型が効くのでtypoしたり忘れるリスクはありません。また、APIのパスが変更になっても関連する利用箇所がすべてコンパイルエラーで落ちます。

既存のバックエンドとは別のaxiosを利用するのも、pluginsでAPI_KEY等を設定した別のインスタンスをInjectすることで問題なく利用できます。

# 補足

上記のようなプロダクトコードですと、microCMSから別サービスに移行したときの変更範囲が甚大になるため、Repositoryパターンを利用するなりして、microCMSを利用していることはVueからは意識しなくていいようにするのが理想ではあります。弊社では手抜きで実装してしまっています。

# まとめ

ぜひAPI型定義ライブラリのaspidaを活用し、型安全に自社製バックエンドとmicroCMSの併用をしましょう。