NuxtJS(with Apollo)のTypeScript対応

# お題
前回、[「frontendに「nuxtjs/apollo」、backendに「go+gqlgen」の組み合わせでGraphQLサービスを作る」](https://qiita.com/sky0621/items/8abd445edba347e8f6f1)というお題で、NuxtJSアプリを作った。（サーバーサイドは単に固定のJSONを返すだけのとりあえず実装だけど）
今回は、リクエストに応じた結果を返すよう、サーバーサイド（Golang）の実装を修正しようと思ったけど、その前に素のJavaScriptでは後々デバッグが厳しくなると思ったのでTypeScript対応しておこうかと。

正直、ほとんど説明レスなので参考になるかどうかは微妙なところ。
記載した環境下において、このような修正を加えると、TypeScript対応ができたという１事例として見てもらえれば。

# 関連記事索引
- 第12回「[GraphQLにおけるRelayスタイルによるページング実装再考(Window関数使用版)](https://qiita.com/sky0621/items/957c010ba01c7a756ef7)」
- 第11回「[Dataloadersを使ったN+1問題への対応](https://qiita.com/sky0621/items/621f075e4257270a9e02)」
- 第10回「[GraphQL(gqlgen)エラーハンドリング](https://qiita.com/sky0621/items/affc833e770fee8b576e)」
- 第９回「[GraphQLにおける認証認可事例（Auth0 RBAC仕立て）](https://qiita.com/sky0621/items/efbb8fa085094230d0fd)」
- 第８回「[GraphQL/Nuxt.js(TypeScript/Vuetify/Apollo)/Golang(gqlgen)/Google Cloud Storageの組み合わせで動画ファイルアップロード実装例](https://qiita.com/sky0621/items/5c2c565678c102d9d4ed)」
- 第７回「[GraphQLにおけるRelayスタイルによるページング実装（後編：フロントエンド）](https://qiita.com/sky0621/items/fdcdaefeef7cf4b750ac)」
- 第６回「[GraphQLにおけるRelayスタイルによるページング実装（前編：バックエンド）](https://qiita.com/sky0621/items/1e8823200633f2c46013)」
- 第５回「[DB接続付きGraphQLサーバ(by Golang)をローカルマシン上でDockerコンテナ起動](https://qiita.com/sky0621/items/58fa2bcfc0935f6186cb)」
- 第４回「[graphql-codegenでフロントエンドをGraphQLスキーマファースト](https://qiita.com/sky0621/items/f03631f0309f5c375b1d)」
- 第３回「[go+gqlgenでGraphQLサーバを作る（GORM使ってDB接続）](https://qiita.com/sky0621/items/32e3e26189589080e492)」
- 第２回「[NuxtJS(with Apollo)のTypeScript対応](https://qiita.com/sky0621/items/0297f5a026be380a5929)」
- 第１回「[frontendに「nuxtjs/apollo」、backendに「go+gqlgen」の組み合わせでGraphQLサービスを作る](https://qiita.com/sky0621/items/8abd445edba347e8f6f1)」

# 開発環境
## # OS - Linux(Ubuntu)
```
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
```

## # フロントエンド
### Nuxt.js
```
$ cat yarn.lock | grep "@nuxt/vue-app"
    "@nuxt/vue-app" "2.11.0"
"@nuxt/vue-app@2.11.0":
  resolved "https://registry.yarnpkg.com/@nuxt/vue-app/-/vue-app-2.11.0.tgz#05aa5fd7cc69bcf6a763b89c51df3bd27b58869e"
```

### パッケージマネージャ - Yarn
```
$ yarn -v
1.19.2
```

### IDE - WebStorm
```
WebStorm 2019.3
Build #WS-193.5233.80, built on November 25, 2019
```

# 参考
https://typescript.nuxtjs.org/ja/guide/
https://github.com/nuxt-community/nuxt-property-decorator

# 実践

手順ひとつひとつを書くよりも前回の記事で出来上がったソースからの差分を見る方がわかりやすいかな。

## TypeScript導入

### ■ package.json

参考にしたサイトに記載のあったいくつかのモジュールを追加。
単純にTypeScript対応するだけでなく、GraphQL用にApolloを使っている関係で`vue-apollo`も追加。

```
diff --git a/frontend/package.json b/frontend/package.json
index 67223cb..b0b174f 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -5,7 +5,7 @@
   "author": "sky0621",
   "private": true,
   "scripts": {
-    "dev": "nuxt",
+    "dev": "nuxt --port 3000",
     "build": "nuxt build",
     "start": "nuxt start",
     "generate": "nuxt generate",
@@ -13,9 +13,13 @@
   },
   "dependencies": {
     "@nuxtjs/apollo": "^4.0.0-rc17",
-    "nuxt": "^2.0.0"
+    "@typescript-eslint/parser": "^2.12.0",
+    "nuxt": "^2.0.0",
+    "nuxt-property-decorator": "^2.5.0",
+    "vue-apollo": "^3.0.2"
   },
   "devDependencies": {
+    "@nuxt/typescript-build": "^0.5.2",
     "@nuxtjs/eslint-config": "^1.0.1",
     "@nuxtjs/eslint-module": "^1.0.0",
     "@nuxtjs/vuetify": "^1.0.0",
```

### ■ nuxt.config.js

```
diff --git a/frontend/nuxt.config.js b/frontend/nuxt.config.js
index 8454218..14392a5 100644
--- a/frontend/nuxt.config.js
+++ b/frontend/nuxt.config.js
@@ -3,7 +3,7 @@ import colors from 'vuetify/es5/util/colors'
 export default {
   mode: 'universal',
   /*
-   ** Headers of the page
+   ** Headers of the pagebuildModules
    */
   head: {
     titleTemplate: '%s - ' + process.env.npm_package_name,
@@ -37,7 +37,8 @@ export default {
   buildModules: [
     // Doc: https://github.com/nuxt-community/eslint-module
     '@nuxtjs/eslint-module',
-    '@nuxtjs/vuetify'
+    '@nuxtjs/vuetify',
+    '@nuxt/typescript-build'
   ],
   /*
    ** Nuxt.js modules
@@ -68,8 +69,8 @@ export default {
   apollo: {
     clientConfigs: {
       default: {
-        // Goサーバを 8080 ポートで起動する予定のため
-        httpEndpoint: 'http://localhost:8080/query'
+        // Goサーバを 5050 ポートで起動する予定のため
+        httpEndpoint: 'http://localhost:5050/query'
       }
     },
     // 任意だけど、これがないとGraphQL的なエラー起きた時に原因が掴みづらいため
@@ -80,9 +81,14 @@ export default {
    ** Build configuration
    */
   build: {
+    babel: {
+      plugins: [
+        ['@babel/plugin-proposal-decorators', { legacy: true }],
+        ['@babel/plugin-proposal-class-properties', { loose: true }]
+      ]
+    }
     /*
      ** You can extend webpack config here
      */
-    extend(config, ctx) {}
   }
 }
```

### ■ tsconfig.json

```
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000..391e78e
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,34 @@
+{
+  "compilerOptions": {
+    "target": "es2018",
+    "module": "esnext",
+    "experimentalDecorators": true,
+    "moduleResolution": "node",
+    "lib": [
+      "esnext",
+      "esnext.asynciterable",
+      "dom"
+    ],
+    "esModuleInterop": true,
+    "allowJs": true,
+    "sourceMap": true,
+    "strict": true,
+    "noEmit": true,
+    "baseUrl": ".",
+    "paths": {
+      "~/*": [
+        "./*"
+      ],
+      "@/*": [
+        "./*"
+      ]
+    },
+    "types": [
+      "@types/node",
+      "@nuxt/types"
+    ]
+  },
+  "exclude": [
+    "node_modules"
+  ]
+}
```

### ■ vue-shim.d.ts

```
diff --git a/frontend/vue-shim.d.ts b/frontend/vue-shim.d.ts
new file mode 100644
index 0000000..eb40980
--- /dev/null
+++ b/frontend/vue-shim.d.ts
@@ -0,0 +1,4 @@
+declare module "*.vue" {
+  import Vue from 'vue'
+  export default Vue
+}
```

### ■ .eslintrc.js

```
diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js
index 6b8177c..f30bc4f 100644
--- a/frontend/.eslintrc.js
+++ b/frontend/.eslintrc.js
@@ -5,7 +5,7 @@ module.exports = {
     node: true
   },
   parserOptions: {
-    parser: 'babel-eslint'
+    parser: '@typescript-eslint/parser'
   },
   extends: [
     '@nuxtjs',
```

### ■ pages/index.vue

```
diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue
index 7587e44..c8a8896 100644
--- a/frontend/pages/index.vue
+++ b/frontend/pages/index.vue
@@ -4,10 +4,13 @@
   </div>
 </template>

-<script>
+<script lang="ts">
+import { Vue, Component } from 'nuxt-property-decorator'
+import 'vue-apollo'
 import TodoCard from '~/components/TodoCard.vue'

-export default {
+@Component({
   components: { TodoCard }
-}
+})
+export default class IndexPage extends Vue {}
 </script>
```

### ■ components/TodoCard.vue

```
diff --git a/frontend/components/TodoCard.vue b/frontend/components/TodoCard.vue
index 5bdbc66..d740996 100644
--- a/frontend/components/TodoCard.vue
+++ b/frontend/components/TodoCard.vue
@@ -25,20 +25,32 @@
   </div>
 </template>

-<script>
+<script lang="ts">
+import { Vue, Component } from 'nuxt-property-decorator'
+import 'vue-apollo'
 import todos from '~/apollo/queries/todos.gql'
-export default {
-  data() {
-    return {
-      todos: []
-    }
-  },

+interface User {
+  id: String
+  name: String
+}
+
+interface Todo {
+  id: String
+  text: String
+  done: Boolean
+  user: User
+}
+
+@Component({
   apollo: {
     todos: {
       prefetch: true,
       query: todos
     }
   }
+})
+export default class TodoCard extends Vue {
+  todos: Todo[] = []
 }
 </script>
```

## TypeScript対応後のソース

実際のソースは対応後のソースを見た方がわかりやすい気がする。
せっかくTypeScript化するので、`class`ベースの書き方にした。

```vue:[pages/index.vue]
<template>
  <div>
    <TodoCard />
  </div>
</template>

<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator'
import 'vue-apollo'
import TodoCard from '~/components/TodoCard.vue'

@Component({
  components: { TodoCard }
})
export default class IndexPage extends Vue {}
</script>
```

```vue:[components/TodoCard.vue]
<template>
  <div>
    <v-row>
      <v-col cols="12" sm="6" offset-sm="3">
        <v-card>
          <v-list two-line subheader>
            <v-list-item v-for="todo in todos" :key="todo.id" link>
              <v-list-item-avatar>
                <v-icon>mdi-gift-outline</v-icon>
              </v-list-item-avatar>
              <v-list-item-content>
                <v-list-item-title>{{ todo.text }}</v-list-item-title>
                <v-list-item-subtitle>{{ todo.done }}</v-list-item-subtitle>
              </v-list-item-content>
              <v-list-item-content>
                <v-list-item-title>
                  {{ todo.user.name }}
                </v-list-item-title>
              </v-list-item-content>
            </v-list-item>
          </v-list>
        </v-card>
      </v-col>
    </v-row>
  </div>
</template>

<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator'
import 'vue-apollo'
import todos from '~/apollo/queries/todos.gql'

interface User {
  id: String
  name: String
}

interface Todo {
  id: String
  text: String
  done: Boolean
  user: User
}

@Component({
  apollo: {
    todos: {
      prefetch: true,
      query: todos
    }
  }
})
export default class TodoCard extends Vue {
  todos: Todo[] = []
}
</script>
```

# まとめ
これで、ローカルで前回と同様に動作確認できた。
今回はGraphQLを介して取得したレスポンスの型を自前で`interface`として定義したけど、GraphQLスキーマから自動生成した方がよいので、そのあたりは次回以降、試みるかも。
あと、`nuxt-config.js`など、結局JSのままにしてるファイルがあるので、そのへんもどうせだったらTSに変えておきたい。

今回の全ソースは下記。
https://github.com/sky0621/study-graphql/tree/v0.2.0