Nuxt.jsのAPI通信で共通のエラーハンドリングを行う

# これはなに

Nuxt.jsでのaspidaを使ったAPI通信について、
API呼び出し箇所でエラーハンドリングするのではなく、共通的にエラーハンドリングする方法を記載します。

また、一部のAPI呼び出しにおいて、例外的に共通のエラーハンドリングを実行しないようにする方法も記載しています。

生のaxiosを使っている場合どうやるかは記載してません。

# 先に結論

- pluginsにてaxios.onError()を設定することで共通のエラーハンドリングをかける
- API呼び出し時のconfigに独自の値を入れることで、共通のエラーハンドリングで処理を分岐できる
- configに独自の値を入れるために型を拡張する必要がある


# やり方

## nuxt.config.js

axiosモジュールの追加と、後述するpluginの読み込みを追加します。

```nuxt.config.js

const nuxtConfig: Configuration = {
  // 略
  plugins: [
    '@/plugins/axios.ts',
  ],
  // 略
  modules: [
    '@nuxtjs/axios',
  ],
  // 略
}
```

## plugins


`plugins/axios.js`を作成して、axiosの設定を追加しましょう。

```plugins/axios.ts
import { Context } from '@nuxt/types'
import { AxiosError, AxiosRequestConfig } from 'axios'
import Rollbar from 'rollbar'


// AxiosRequestConfigを拡張して、Nuxtのエラーページを表示するかどうかのフラグを保持できるようにする
export type CustomRequestConfig = AxiosRequestConfig & {
  dontDisplayErrorPage?: boolean
}

export default ({ $axio, error }: Context) => {
  // ここでaxiosの共通設定を行う
  $axios.defaults.timeout = 10000
  // NOTE: ここでCustomRequestConfigのプロパティもデフォルト値を設定できれば良いが、型の制約上難しそうだった

  // axiosのエラーハンドリングを追加する
  $axios.onError((axiosError: AxiosError) => {
    const res = axiosError.response
    const status = res?.status ?? 500

    if ((axiosError.config as CustomRequestConfig)?.dontDisplayErrorPage) {
      // api利用時にconfigとして`dontDisplayErrorPage: true`を渡した時のみエラーページの表示をスキップする
      return
    }

    // 共通で実行したいエラー処理
    error({ statusCode: status }) // エラーページを表示
  })
}
```

### 共通エラー処理追加

`$axios.onError()`でエラー時の共通処理を書くことができます。
ただし、このままだと全てのAPI呼び出しにおいて、エラー時に同じ処理をしてしまうので、あるAPIだけ違った処理にしたい場合などに対応できません。
そこで、axios呼び出し時のconfigに独自の設定値を渡し、onErrorのタイミングで取得することで処理の分岐を実現します。

### axiosの設定値(AxiosRequestConfig)型の拡張

```ts
export type CustomRequestConfig = AxiosRequestConfig & {
  dontDisplayErrorPage?: boolean
}
```
このようにしてプロパティを追加することができます。

## api呼び出すところ

### 共通エラーハンドリングを利用する
共通エラーハンドリングをしたい場合は、普通にaspidaを使ってAPIを呼び出すだけです。
エラーが発生したら、共通のエラーハンドリングが実行されます。


### 例外的にAPI別にエラーハンドリングをする

例外的に共通エラーハンドリングをスキップしたい場合は以下のようにします。

```call_api.ts
// 呼び出し処理の例
import api from '@@/apis/$api'
import { AxiosInstance } from 'axios'
import aspida from '@/libs/aspida-shim'


export const callAPI = (axios: AxiosInstance) => {
  // <T>にカスタマイズした設定型を入れる
  const client = api<CustomRequestConfig>(aspida(axios))

  client.api.hogehoge.$get({
    config: {
      dontDisplayErrorPage: true // 独自のエラーハンドリングをするのでエラーページの表示をしない
      timeout: 60000, //通常のオプションも上書きできる
    }
  })
  .then(() => {
     console.log('success!')
  })
  .catch((error: AxiosError) => {
     console.dir(error)
     // ここに自前でエラーハンドリングを書く
     // `dontDisplayErrorPage` オプションをつけているのでエラーページの表示処理はスキップされる
  })
}
```


### 説明


typescriptを使用している場合は、独自のconfig値を渡そうとしても
configの型が`AxiosRequestConfig`型でないためエラーになってしまいます。
これを解決するには、以下のようにします。

```
  // <T>にカスタマイズした設定型を入れる
  const client = api<CustomRequestConfig>(aspida(axios))
```

ここの`api` = `'@@/apis/$api'`はaspidaがビルドしたAPIの型情報が記載されたファイルです。
apiは以下のような型定義になっていて、Tに渡した型が、configの型となることがわかります。

```@@/apis/$api.ts
const api = <T>({ baseURL, fetch }: AspidaClient<T>) => {
// 略
  return {
    api: {
      hogehoge: {
        get: (option?: { config?: T }) =>
          fetch<Methods0['get']['resBody']>(prefix, PATH0, GET, option).json(),
// 略
```

`<T>`カスタマイズした設定オブジェクトの型（CustomRequestConfig）を入れてやれば
独自に定義した設定値を使うことができます。

# まとめ

- pluginsにてaxios.onError()を設定することで共通のエラーハンドリングをかける
- API呼び出し時のconfigに値を入れることで、共通エラーハンドリングで処理を分岐できる
- configに独自の値を入れるために型を拡張する必要がある

以上、参考になりましたら幸いです。