(Nuxt + Composition API) RepositoryFactoryパターンを用いたAPIリクエストの実装サンプル
1. 概要
RepositoryFactoryパターン(VueにおけるAPI呼び出しのデザインパターン)の実装例を、Nuxt + Composition API + TypeScript の構成で紹介します。
実装例のリポジトリはこちらになります。
1.1. 本記事の背景
【フォースタ テックブログ】RepositoryFactoryパターンをVueのAPIリクエストに導入する | TechLab. blog にRepositoryFactoryパターンの非常にわかりやすい説明と、実装の一部が紹介されています。
上記記事を参考に Nuxt + Composition API + TypeScript の構成で実装する際、型の指定などでつまづくところがあったため、問題解決の備忘録として本記事を執筆しました。
ツール、ライブラリのバージョンは下記の通りです。
項目 | ver |
---|---|
Node.js | 15.9.0 |
Yarn | 1.22.11 |
Nuxt | 2.15.8 |
2. 詳細
2.1. 事前に必要なもの
Node.js、Yarn のインストール(Yarn は npm でも可。npmの場合は適宜読み替えてください。)
2.2. Nuxt プロジェクトのセットアップ
2.2.1. プロジェクトを作成
$ yarn create nuxt-app <project-name>
<project-name>
は好きな名前を指定してください。
いくつか質問されるので答えていきます。今回は下記のように設定しました。
❯ yarn create nuxt-app repository-factory-pattern-sample
yarn create v1.22.11
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
[4/4] 🔨 Building fresh packages...
success Installed "create-nuxt-app@3.7.1" with binaries:
- create-nuxt-app
[##################################################################################################################################] 342/342
create-nuxt-app v3.7.1
✨ Generating Nuxt.js project in repository-factory-pattern-sample
? Project name: repository-factory-pattern-sample
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: None
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Linting tools: ESLint, Prettier
? Testing framework: None
? Rendering mode: Single Page App
? Deployment target: Static (Static/Jamstack hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Continuous integration: None
? Version control system: Git
プロジェクト作成完了後、ディレクトリを移動(cd <project-name>
)して下記コマンドで Nuxt のバージョンを確認することができます。
❯ npx nuxt -v
@nuxt/cli v2.15.8
また、下記コマンドでアプリケーションを立ち上げ、localhost にアクセスして動作するか確認しておきます。
❯ yarn dev
図のような画面が表示されればOKです。
2.2.2. ライブラリのインストール
axios と Composition API のライブラリを追加します。
$ yarn add @nuxtjs/axios
$ yarn add @nuxtjs/composition-api
ライブラリ追加後、nuxt.config.js
に設定を追記します。
// nuxt.config.js
export default {
...,
buildModules: [
'@nuxt/typescript-build',
'@nuxtjs/composition-api/module' // <- これを追記
],
...,
modules: [
'@nuxtjs/axios' // <- これを追記
]
}
また、モックAPIサーバーとして json-server を追加します。
$ yarn add -D json-server
プロジェクトルートにdb.json
ファイルを作成します
// db.json
{
"user": [
{ "id": 1, "name": "test-user1", "mail": "test-user1@test.com" },
{ "id": 2, "name": "test-user2", "mail": "test-user2@test.com" },
{ "id": 3, "name": "test-user3", "mail": "test-user3@test.com" }
],
"books": [
{ "id": 1, "title": "test1", "author": "test-user1" },
{ "id": 2, "title": "test2", "author": "test-user2" },
{ "id": 3, "title": "test3", "author": "test-user3" }
]
}
package.json
にモックサーバーと同時に起動するスクリプトを追記します。
// package.json
{
...,
"scripts": {
...,
"dev:server": "json-server --watch ./db.json nuxt --port 3001 & nuxt", // <- 追記
...
},
...
}
yarn dev:server
を実行して、localhost:3001/user
などにアクセスし、正常にレスポンスが返ってくればOKです。
各リソースごとのRepositoryの作成
今回はモックデータにuser
とbooks
というリソースを準備したので、それぞれRepositoryを作成します。
// /composables/repositories/userRepository.ts
import { NuxtAxiosInstance } from "@nuxtjs/axios";
const resource = '/user'
export const UserRepository = ($axios: NuxtAxiosInstance) => ({
get() {
return $axios.$get(`${resource}`)
}
})
// /composables/repositories/booksRepository.ts
import { NuxtAxiosInstance } from '@nuxtjs/axios'
const resource = '/books'
export const BooksRepository = ($axios: NuxtAxiosInstance) => ({
get() {
return $axios.get(`${resource}`)
}
})
今回はgetメソッドしか定義していませんが、必要に応じてリソースごとにapiリクエストのメソッドを追加していきます。
Factoryの作成
Repository作成後、Factoryを作成していきます。
// /composables/factories/apiRepositoryFactory.ts
import { UserRepository } from "../repositories/userRepository";
import { BooksRepository } from "../repositories/booksRepository";
export interface Repositories {
user: typeof UserRepository
books: typeof BooksRepository
}
const repositories: Repositories = {
user: UserRepository,
books: BooksRepository
}
export const apiRepositoryFactory = {
get: (key: keyof Repositories) => repositories[key]
}
Repository plugin と axios plugin の作成
各Repositoryを呼び出すためのpluginを作成します。
import { Inject, NuxtApp } from '@nuxt/types/app'
import {
apiRepositoryFactory,
Repositories
} from '@/composables/factories/apiRepositoryFactory'
export default ({ app }: { app: NuxtApp }, inject: Inject) => {
const repositories = (name: keyof Repositories) => {
return apiRepositoryFactory.get(name)(app.$axios)
}
inject('repositories', repositories)
}
また、axiosの共通共通処理などを書いておきます。
// /plugins/axios.ts
import { NuxtApp } from '@nuxt/types/app'
const baseUrl = 'http://localhost:3001'
export default ({ app }: { app: NuxtApp }) => {
app.$axios.setBaseURL(baseUrl)
app.$axios.onRequest((config) => {
console.log(config)
})
app.$axios.onResponse((config) => {
console.log(config)
})
app.$axios.onError((e) => {
console.log(e.response)
})
}
pluginファイルを作成後、nuxt.config.js
に設定を加えます。
// nuxt.config.js
export default {
...,
plugins: [
'@/plugins/repository.ts',
'@/plugins/axios.ts'
],
...
}
サンプルページを作成し、apiを呼び出す
/pages/sample.vue
を作成し、apiを呼び出してみます。
<template>
<div>
<h2>Users</h2>
<ul>
<li v-for="(user, index) in users" :key="index">
{{ user.name }}
</li>
</ul>
<h2>Books</h2>
<ul>
<li v-for="(book, index) in books" :key="index">
{{ book.title }}
</li>
</ul>
</div>
</template>
<script lang="ts">
import { defineComponent, useContext, useAsync } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const { app } = useContext()
const users = useAsync(async () => {
const response = await app.$repositories('user').get()
return response.data
})
const books = useAsync(async () => {
const response = await app.$repositories('books').get()
return response.data
})
return {
users,
books,
}
},
})
</script>
localhost:3000/sample
にアクセスすると次のような画面が表示され、各リソースのデータが取得できていることが確認できます。
これでapi呼び出しまで行えるようになりましたが、まだ問題が残っています。
現状 app.$repositories(...)
の型が any
となっており、app.$repositories('hogehoge')
などと存在しないリソースを指定しても実行するまでエラーになりません。
仕上げとして最後に $repositores
の型定義を行います。
型定義ファイルを作成する
/types/
ディレクトリを作成し、その中に vue.d.ts
ファイルを作成します。
// /types/vue.d.ts
import { Repositories } from '@/composables/factories/apiRepositoryFactory'
declare module '@nuxt/types' {
interface NuxtAppOptions {
readonly $repositories: <K extends keyof Repositories>(key: K) => ReturnType<Repositories[K]>
}
}
定義ファイル作成後は、一旦vscodeをリロードしておきましょう。
再度sample.vue
を見てみると、無事に型定義できていることがわかります。
これで完了となります。
Discussion