Nuxt.js + GraphQL + Ruby on Railsで作るToDoアプリチュートリアル(後編)
2019/08/25
Table of Contents
ソースコード
以下に公開しています。 バックエンド側は前編で実装済みです。
今回はフロントエンド側を実装します。
バックエンド側
フロントエンド側
動作環境
最新版のNode.jsをインストールした状態からスタートします。
$ node -v
v12.9.0
Nuxt.jsのプロジェクトを作成
create-nuxt-appを使い、 rails_nuxt_grapshql_todoapp_front
という名前のプロジェクトを作成します。
以下のようにコマンドを実行し、選択していきます。
$ npx create-nuxt-app rails_nuxt_grapshql_todoapp_front [~]
create-nuxt-app v2.8.0
✨ Generating Nuxt.js project in /Users/yokazaki/rails_nuxt_grapshql_todoapp_front
? Project name rails_nuxt_grapshql_todoapp_front
? Project description This is a tutorial for creating a ToDo app.
? Author name Yuhei Okazaki
? Choose the package manager Npm
? Choose UI framework Vuetify.js
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules Progressive Web App (PWA) Support
? Choose linting tools ESLint
? Choose test framework None
? Choose rendering mode Single Page App
実行し終わったら早速起動してみましょう。
$ cd rails_nuxt_grapshql_todoapp_front
$ npm run dev
http://localhost:3000 へアクセスすると初期画面が表示されます。
apollo-moduleの追加
今回、GraphQLのクライアントとしてapollo-moduleを使用します。
以下コマンドでインストールできます。
$ npm install --save @nuxtjs/apollo
$ npm install --save graphql-tag
vuetify-moduleの更新
今回、デザインコンポーネントとしてVuetifyを使っていますが、create-nuxt-app
でインストールされるvuetify-moduleのバージョンがかなり古いものであったため更新しておきます。
$ npm install --save @nuxtjs/vuetify@v1.3.3
nuxt.config.jsを追記
nuxt.config.jsを追記します。
追記内容としては「apollo-moduleの設定」と「ホスト名の設定(ポート番号)」の2点です。
ホスト名は前編の最後に行ったCORS設定に合わせています。
import colors from 'vuetify/es5/util/colors'
export default {
mode: 'spa',
/*
** Headers of the page
*/
head: {
titleTemplate: '%s - ' + process.env.npm_package_name,
title: process.env.npm_package_name || '',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{
rel: 'stylesheet',
href:
'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons'
}
]
},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: [
],
/*
** Plugins to load before mounting the App
*/
plugins: [
],
/*
** Nuxt.js modules
*/
modules: [
'@nuxtjs/vuetify',
'@nuxtjs/pwa',
'@nuxtjs/eslint-module',
'@nuxtjs/apollo' // ★追加
],
/*
** vuetify module configuration
** https://github.com/nuxt-community/vuetify-module
*/
vuetify: {
theme: {
primary: colors.blue.darken2,
accent: colors.grey.darken3,
secondary: colors.amber.darken3,
info: colors.teal.lighten1,
warning: colors.amber.base,
error: colors.deepOrange.accent4,
success: colors.green.accent3
}
},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {
}
},
// ★ ここから追記
apollo: {
clientConfigs: {
default: {
httpEndpoint: 'http://localhost:3000/graphql'
}
}
},
server: {
port: 8080
}
// ★ ここまで追記
}
GraphQL QueryとMutationを定義
apollo-moduleが使用するGraphQL QueryとMutationをファイルに記述していきます。
apollo/queries/tasks.gql
query {
tasks {
id
title
description
completed
}
}
apollo/mutations/createTask.gql
mutation($title: String!, $description: String!) {
createTask( input: { title: $title, description: $description }) {
task {
id
title
description
completed
}
}
}
apollo/mutations/updateTask.gql
mutation($id: ID!, $completed: Boolean!) {
updateTask( input: { id: $id, completed: $completed }) {
task {
id
title
description
completed
}
}
}
apollo/mutations/deleteTask.gql
mutation($id: ID!) {
deleteTask( input: { id: $id }) {
task {
id
title
description
completed
}
}
}
ページを実装
ページのデザインおよびTaskの作成、更新、削除処理を実装します。
まずは、layouts/default.vueを編集して、ヘッダー・フッターのデザインを整えます。
<template>
<v-app>
<v-toolbar fixed>
<v-toolbar-title v-text="title" />
</v-toolbar>
<v-content>
<nuxt />
</v-content>
<v-footer center>
<v-layout justify-center>
<span>© 2019 Yuhei Okazaki. All Rights Reserved.</span>
</v-layout>
</v-footer>
</v-app>
</template>
<script>
export default {
data() {
return {
title: 'Tasks'
}
}
}
</script>
次にpages/index.vueを編集します。
<template>
<v-container>
<v-row>
<v-col cols="6" offset="3">
<v-card>
<v-card-title>Register Task</v-card-title>
<v-card-text>
<v-form>
<v-container>
<v-row>
<v-col cols="6">
<v-text-field v-model="newTask.title" label="Title" required />
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<v-text-field v-model="newTask.description" label="Description" required />
</v-col>
</v-row>
<v-row>
<v-col>
<v-btn @click="createTask(newTask)">
Register
</v-btn>
</v-col>
</v-row>
</v-container>
</v-form>
</v-card-text>
</v-card>
</v-col>
</v-row>
<v-row v-for="task in tasks" :key="task.id">
<v-col cols="6" offset="3">
<v-card>
<v-card-title>
<v-checkbox v-model="task.completed" :label="task.title" @click="updateTask(task)" />
<v-btn text icon @click="deleteTask(task)">
<v-icon>clear</v-icon>
</v-btn>
</v-card-title>
<v-card-text>{{ task.description }}</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script>
import tasks from '~/apollo/queries/tasks'
import createTask from '~/apollo/mutations/createTask'
import deleteTask from '~/apollo/mutations/deleteTask'
import updateTask from '~/apollo/mutations/updateTask'
export default {
data() {
return {
tasks: {},
newTask: {
title: '',
description: ''
}
}
},
methods: {
async createTask(task) {
try {
await this.$apollo.mutate({
mutation: createTask,
variables: {
id: task.id,
title: task.title,
description: task.description
},
refetchQueries: [{
query: tasks
}]
})
this.newTask = { title: '', description: '' }
} catch (e) {
window.console.log(e)
}
},
async deleteTask(task) {
try {
await this.$apollo.mutate({
mutation: deleteTask,
variables: {
id: task.id
},
refetchQueries: [{
query: tasks
}]
})
} catch (e) {
window.console.log(e)
}
},
async updateTask(task) {
try {
await this.$apollo.mutate({
mutation: updateTask,
variables: {
id: task.id,
completed: !task.completed
},
refetchQueries: [{
query: tasks
}]
})
} catch (e) {
window.console.log(e)
}
}
},
apollo: {
tasks: {
query: tasks
}
}
}
</script>
ソースコードの内容を大まかに解説します。
コードが少し長いですが複雑な処理はしていないので、コードと照らし合わせて理解してみてください。
- 保持するデータはタスク一覧(
tasks
)と新規登録用タスク(newTask
)のみ - ページ表示時点で
tasks
のQueryを実行して、タスク一覧をフェッチする - フォームのRegisterボタンをクリックしたときに
createTask()
を呼び出し、createTask
のMutationを実行、その後tasks
Queryを再実行する - タスクのチェックボックスを操作すると
updateTask()
を呼び出し、updateTask
のMutationを実行、その後tasks
Queryを再実行する - タスクの☓ボタンをクリックすると
deleteTask()
を呼び出し、deleteTask
のMutationを実行、その後tasks
Queryを再実行する
動作確認
以上で実装は完了なので、動かしてみましょう!
まず、バックエンド側を起動します。
// バックエンドのプロジェクトへ移動
$ cd rails_nuxt_grapshql_todoapp // 移動先パスは各自の環境に合わせてください
$ rails s
次に別のTerminalでフロントエンド側を起動します。
// フロントエンドのプロジェクトへ移動
$ cd rails_nuxt_grapshql_todoapp_front // 移動先パスは各自の環境に合わせてください
$ npm run dev
http://localhost:8080へアクセスするとToDoアプリが表示されます。
お疲れ様でした。
まとめ
本チュートリアルではバックエンド側にRuby on Rails、フロントエンド側にNuxt.jsを用いて、GraphQLによるデータの同期を実現することでToDoアプリを作成しました。
これまでフロントエンドに詳しくない人でも、ToDoアプリを作ったことである程度仕組みを理解し、自信を得られたかと思います。
バリデーションを追加したり、ドラッグ・アンド・ドロップで並び替えられるようにしたり、ユーザのログイン/ログアウト機能を付けたりとまだまだ実装の余地はありますのでぜひチャレンジしてみてください。
(これらは、需要があれば別の記事で解説したいと思います)