aspidaでフロントエンドからAPIを見える化する


Nuxt.js(TypeScript)で開発していく際に[aspida](https://github.com/aspidajs/aspida)というOSSを使用すると開発が捗ったので紹介します

主に以下のような不満を解消してくれるものになると思います

- APIも型定義したい
- フロントから叩いているAPI一覧が分からん
- フロントで利用するAPIを一元管理したい
- APIの返り値が分からんからconsole.log()で出力してみるってのを毎回やってる...

## aspidaとは
ギリシャ語で「盾」って意味らしいです
型安全な HTTP クライアントラッパーを提供してくれるOSSです
[日本語ドキュメント](https://github.com/aspidajs/aspida/blob/master/packages/aspida/docs/ja/README.md)のページでかなり詳細に説明されていました

[こちらに使用例](https://github.com/aspidajs/aspida/tree/master/packages/aspida/samples)まで用意されています

APIのInterfaceを静的に定義できるので、VSCodeで補完が効くようになったり、APIが仕様変更で返り値やエンドポイントが変更になっても血眼でgrepせずともapis/ 以下の定義を変えれば良くなります :clap: :clap: 

個人的に好きなのは
APIのパス定義がNuxtのpages/ と同じような作りで見やすい点です

例えば、
任意のユーザの任意の記事の取得と更新をするエンドポイントがそれぞれ

```
GET
users/:user_id/article/:article_id
PUT
users/:user_id/article/:article_id

user_id    => autoincrementで生成される数字
article_id => ハッシュ値
```
で定義されていたとします
その際クライアントサイドでは以下の様なディレクトリを切って

```bash
/apis                          <- /apis以下に作られる（一応設定で変えられる
|--$api.ts                     <- ビルドするとAPI定義が生成されるファイル
|--users
   |--_userId@number           <- 変数の型も定義できる
      |--articles
         |--_articleId@string
            |--index.ts        <- このファイルでAPIのやりとりを定義する
```
index.tsでは以下のように書きます

```ts:index.ts
export type Article = {
  id?: string
  user_id?: string
  title: string
  body: string
}

export interface Methods {
  get: {
    // APIの返り値
    resBody: Article
  }
  put: {
    // put時に投げる
    reqBody: {
      title: string
      bodyParser: string
    }
    // APIの返り値
    resBody: Article
  }
}
```

そして、`aspida --build `を叩きます
めんどくさいのでscriptsにコマンド登録しておくと良いです

```js:package.json
  "scripts": {
    "api": "aspida --build",
  },
```

コマンド登録して`npm run api`コマンドが無事成功すると
apis/$api.ts was built successfully. と出力されます
これで準備完了です

`apis/$api.ts`を見にいくとなにやらいっぱい書かれていると思いますが
このファイルはlockファイルみたいなもので、触っちゃいけません


## アプリケーションでの利用

アプリケーションではインポートして利用するのですが、
pluginでinjectしておくと便利です
pluginでinjectするやり方は、[こちらの記事](https://qiita.com/m_mitsuhide/items/c0cce7f3a79907c6e75c)(aspidaの作者さんの記事です)がとても参考になるのでここでは省きます
設定が済めば、以下のようにappやthisのコンテキストから利用できます

```vue
<script lang="ts">
import Vue from 'vue'

~(略)~

export default Vue.extend({
  async asyncData ({ app, params, error }) {
    const article = await app.$api.users
      ._userId(parseInt(params.userId))
      .articles
      ._articleId(params.articleId)
      .$get()
      .catch(() => { error({ statusCode: 404 }) });

    return { article };
  },

  methods: {
    submit (article: Article) {
      this.$api.users
        ._userId(parseInt(this.$store.state.user.id))
        .articles
        ._articleId(this.$route.params.articleId)
        .put({ data: article })
        .then(({ data }) => {
          if (data.statusCode === 200) ...
          if (data.statusCode === 204) ...
        })
        .catch((err) => {
          ...
        })
    }
  }
</script>
```

既存のプロジェクトで`this.$axios`とかでAPIとやりとりしている箇所はもちろんそのまま機能するので、
徐々に移行できる点が良いですね

あのエンドポイントだけはめちゃくちゃ複雑だから型定義しておきたい...
のような需要も満たしてくれそうです :clap: :clap: 

また、上記のサンプルコードで`$get()`や`post()`など`$`が付いたり付かなかったりのHTTP Methodが出てきました
これは`apis/$api.ts`の中を見るとわかるのですが、
Response.dataが欲しいかResponseが欲しいかって違いです

Response.metaから取れるメタ情報からハンドリングをする必要があれば`$`なしを利用して、
単純にデータが欲しければ`$`ありを利用する感じです

また、`$`ありを利用すれば
axiosを使っていると、こんな感じで冗長になりがちなコードが

```ts
const user = app.$axios.$get(`/users/${params.userId}`).then(({ data }) => { return data })
```

このようにスッキリします :thumbsup: 

```ts
const user = app.$api.users._userId(params.userId).$get();
```

---

TypeScriptを導入済みならすぐにでもaspidaは導入できるし、徐々に移行していこうってスタンスが取れるのがaspidaの良いところかなと思います
APIの型定義の際に手動でCLIを叩きますが現在、[Swaggerをaspidaの型定義に変換するモジュールを開発中](https://qiita.com/m_mitsuhide/items/9ce4e048749075845655)のようです
フロントから利用するHTTPクライアントが型安全になる世界が来るのも遅くはなさそうですね :clap: :clap: 

また、aspidaは最近公開されたOSSでContributeどしどし募集しているようです :muscle: :muscle: 

[GitHub: aspida日本語ドキュメント](https://github.com/aspidajs/aspida/tree/master/packages/aspida/docs/ja)