お題
前回、「frontendに「nuxtjs/apollo」、backendに「go+gqlgen」の組み合わせでGraphQLサービスを作る」というお題で、NuxtJSアプリを作った。(サーバーサイドは単に固定のJSONを返すだけのとりあえず実装だけど)
今回は、リクエストに応じた結果を返すよう、サーバーサイド(Golang)の実装を修正しようと思ったけど、その前に素のJavaScriptでは後々デバッグが厳しくなると思ったのでTypeScript対応しておこうかと。
正直、ほとんど説明レスなので参考になるかどうかは微妙なところ。
記載した環境下において、このような修正を加えると、TypeScript対応ができたという1事例として見てもらえれば。
関連記事索引
- 第12回「GraphQLにおけるRelayスタイルによるページング実装再考(Window関数使用版)」
- 第11回「Dataloadersを使ったN+1問題への対応」
- 第10回「GraphQL(gqlgen)エラーハンドリング」
- 第9回「GraphQLにおける認証認可事例(Auth0 RBAC仕立て)」
- 第8回「GraphQL/Nuxt.js(TypeScript/Vuetify/Apollo)/Golang(gqlgen)/Google Cloud Storageの組み合わせで動画ファイルアップロード実装例」
- 第7回「GraphQLにおけるRelayスタイルによるページング実装(後編:フロントエンド)」
- 第6回「GraphQLにおけるRelayスタイルによるページング実装(前編:バックエンド)」
- 第5回「DB接続付きGraphQLサーバ(by Golang)をローカルマシン上でDockerコンテナ起動」
- 第4回「graphql-codegenでフロントエンドをGraphQLスキーマファースト」
- 第3回「go+gqlgenでGraphQLサーバを作る(GORM使ってDB接続)」
- 第2回「NuxtJS(with Apollo)のTypeScript対応」
- 第1回「frontendに「nuxtjs/apollo」、backendに「go+gqlgen」の組み合わせでGraphQLサービスを作る」
開発環境
# 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
ベースの書き方にした。
<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>
<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