【Nuxt.js】axiosのAPI共通処理をラップし、Repositoryパターンを実現する(TypeScript)
2022-03-28やりたいこと
Nuxt.jsのaxiosでサーバーのREST APIを呼び出す処理を、classにまとめて、いろんなコンポーネントからthisで呼び出せるようにしたい
例)
APIのカテゴリー毎にRepositoryクラスを作って・・・・。
1 2 3 4 5 6 7 8 9 10 11 12 |
class UserRepository { // 取得処理 public async get(): Promise<ApiResult<User>> { const res = await this.axios.$get('/user') return new ApiResult(res) } // 編集処理 public async put(user: User) { const res = await this.axios.$put(`/user/${user.$id}`, user) return new ApiResult(res) } } |
componentからthisで呼び出せるようにする!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
created() { this.getUsers() }, methods:{ async getUsers() { // 取得API呼び出し const res = await this.$userRepository.get() this.userList = res.$data }, async updateUser(user: User) { // 更新API呼び出し await this.$userRepository.put(user) } } |
1.axiosをインストールする
以下コマンドでaxiosを入れる。
1 |
yarn add @nuxtjs/axios |
package.jsonにaxiosが入っていることを確認
■/package.json
1 2 3 4 5 |
"dependencies": { "@nuxt/typescript-runtime": "^1.0.0", "@nuxtjs/axios": "^5.12.2", // ←コレ "nuxt": "^2.14.0" }, |
2.axiosをモジュール登録する
@nuxtjs/axiosが有効になるように、nuxt.config.jsの設定をします。
■/nuxt.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
module.exports = { ・ ・ // modulesの配列に、@nuxtjs/axiosを追加する。 modules: ['@nuxtjs/axios’], ・ ・ // axiosのoption設定(必要があれば追記する) axios: { baseURL: 'http://localhost:8000/api', credentials: true // withCredentialsが必要な場合 }, ・ ・ }; |
3.repositoryフォルダを作成し、UserRepository.tsを作成する。
まずは、repositoryフォルダ(apiフォルダでもdomainフォルダでも名前は自由)を作成し、Userに関するAPI処理(api通信し、レスポンスを特定の型に整形してコンポーネントへ返す)を行うRepositoryクラスを作成しておきます。
■/repository/UserRepository.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { NuxtAxiosInstance } from '@nuxtjs/axios' export default class UseRepository { private readonly axios: NuxtAxiosInstance private routePath = '/user' // ★3-1pluginでnewして使用する。 constructor (axios: NuxtAxiosInstance) { this.axios = axios } public async get() { const res = await this.axios.get(this.routePath) return new ApiResult(res.data) } public async put(user: User) { const res = await this.axios.put(`${this.routePath}/${user.$id}`, user) return new ApiResult(res.data) } } |
Repositoryクラスは★3-1のように、pluginからnew(引数NuxtAxiosInstance)して使えるようにしておきます。
4.pluginsフォルダにaxios.tsを作成する。
処理を共通化するために、プラグインを作成します。
まず、plugins/axios.tsを作成して、共通のヘッダーの設定や、レスポンスが帰ってきた時の共通処理を記載しておきます。
■plugins/axios.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
// 1.Plugin型でプラグイン本体を作成 import { Plugin } from '@nuxt/types' import UserRepository from '@/repository/UserRepository' const repositoryPlugin: Plugin = (context, inject) => { // 2.axiosインスタンスを生成する const api = context.$axios.create({ baseURL: process.env.apiBaseUrl || 'http://localhost:8000', // baseURLは、envから取得すると便利 headers: { common: { Accept: 'application/json', }, 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/json', }, data: {}, }) // 3.onErrorを使用して、サーバーからエラーレスポンスが返ってきた時の処理を記載する。 api.interceptors.response.use( function (response) { return response }, function (error) { if (!error.response) { console.log(error) return error } // サーバーサイドからのレスポンスが、404の時は404ページに飛ぶ。等の操作ができる if (error.response.status === 404) { context.redirect('/404') } return error.response } ) // 4.Repositoryクラスをinjectする。 const userRepository = new UserRepository(context.$axios) inject('userRepository', userRepository) } export default repositoryPlugin // 5.Vueインスタンスや、Contextの型を拡張する。 declare module 'vue/types/vue' { interface Vue { readonly $userRepository: UserRepository } } declare module '@nuxt/types' { interface NuxtAppOptions { readonly $userRepository: UserRepository } interface Context { readonly $userRepository: UserRepository } } |
長いので、ブロック毎に区切って説明します。
1.Plugin型でプラグイン本体を作成
Repositoryクラスもimportしておく。Repositoryクラスが増えた時は、随時ここにimportを追加していきます。
1 2 3 4 |
import { Plugin } from '@nuxt/types' import UserRepository from '@/domain/users/UserRepository' const repositoryPlugin: Plugin = (context, inject) => { |
2.setHeaderを利用して、リクエスト時のヘッダーを指定する
Interceptorsという機能を利用し、axiosの共通処理を作成していきます。
setHeader を使用すると、リクエストするときのヘッダーを指定することができます。
1 2 3 |
$axios.setHeader('Accept', 'application/json') $axios.setHeader('X-Requested-With', 'XMLHttpRequest') $axios.setHeader('Content-Type', 'application/json') |
3.onErrorを使用して、サーバーからエラーレスポンスが返ってきた時の処理を記載する。
必要であれば、Interceptorsの onError を利用して、サーバーからレスポンスが返ってきた時の処理を共通化することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$axios.onError((error: AxiosError) => { if (error.response === undefined) { // エラーレスポンスがない、net::ERR_CONNECTION_REFUSEDなどは、ここで処理する redirect('/500') } const code = error.response && error.response.status // 認証エラー if (code === 401) { redirect('/401') } // 404エラー if (code === 404) { redirect('/404') } }) |
4.Repositoryクラスをinjectする。
ここが一番のミソです。
UserRepositoryに NuxtAxiosInstance を引数に渡してインスタンス化し、injectします。
inject を使うことで、Nuxt.jsのいろいろな場所から共通で利用したい関数や値を this.$XXXX で呼び出すことができます。
1 2 3 4 5 |
const userRepository = new UserRepository($axios) inject('userRepository', userRepository) } export default repositoryPlugin |
コンポーネントから import でripositoryを読み込むのもアリですが、injectを使えば、Repositoryの差し替えなどが発生した場合楽になります。(このファイルだけ書き直せばいいので。)
参考) Nuxt.jsのinjectを使ってDIする – CYDAS Developer’s Blog
5.Vueインスタンスや、Contextの型を拡張する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//★5−1 declare module 'vue/types/vue' { interface Vue { readonly $userRepository: UserRepository } } // ★5-2 declare module '@nuxt/types' { interface NuxtAppOptions { readonly $userRepository: UserRepository } interface Context { readonly $userRepository: UserRepository } } |
5を書かなくても動くのですが、TypeScriptで開発している以上、型定義が必須となります。
コンポーネント上で呼び出した時、VSCode上にも Property '$userRepository' does not exist on type 'CombinedVueInstance<Vue,・・ というエラーが出てしまいます。
なので、Vueインスタンス上で $userRepository を使用できるように、型を拡張します。(★5-1)
Repositoryクラスが増えた場合は readonly $xxxRepository: XxxRepository を追加していきます。
参照 )プラグインで使用するための型拡張
あわせて、this.$nuxt.context からも使用できるように、’@nuxt/types’の型も拡張します(★5-2)
これで、コンポーネントからUserAPIを使用する準備が整いました。
5.コンポーネントから呼び出す。
メソッドで呼び出す場合(thisで呼び出す)
1 2 3 4 5 6 7 8 9 |
methods:{ async getUser() { const res = await this.$userRepository.$get() this.userList = res.$data }, async updateUser(user: User) { await this.$userRepository.put(user) } } |
asyncDataなどから呼び出す場合(contextで呼び出す)
1 2 3 4 5 6 |
async asyncData(context) { const userList = context.$orderRepository.$get() return { userList } }, |
以上で、やりたかったことを実現することができました!
6.まとめ
axiosの処理を共通化する際、いままで、以下のサイトを参考にしていました。
Nuxt.jsでaxiosの共通処理を作成し、API呼び出し処理をラップして使用する – Qiita
ただ、
・ESLintを入れた際に、 export let axios の部分が、 import/no-mutable-exports エラーとなってしまう
・各コンポーネントでimportする必要がでてくる
などのモヤモヤで、悩んでいたところ、山地 さん (@tom_nobu) / Twitter がinjectを使ってfirebaseの処理を共通化していたのを見て真似してみました!感謝🙏
あとは、injectしてしまうと、Repositoryクラスのメソッドの参照がVSCodeの機能で追えなくなっちゃうのでどこからRepositoryを使っているのか分からなくなってしまうのを解決したいですね・・。
でも、今のところ、Repositoryクラスはpagesからのみ使用する!と決めているので特に支障はなくこのやり方で進めていきたいと思います!
あとは、サーバーサイドから送られてくるanyのデータをどうやってModelに落とし込むか、ベストな方法を検討していきたい。
そちらが解決したら、素敵なVue+TSライフを送れそうな気がする!
—————
この記事がお役に立てたら、是非シェアをお願いします^^