saba1024のブログ

どうでも良い思いついた事とかプログラミング関係のメモとか書いていきます。

Grails/Vue.jsでTodoアプリ(CRUD)

これはG* Advent Calendar 2017の8日目の記事です。

この記事は、以下の5つの投稿で成り立っています。

Grails/Vue.jsでTodoアプリ(環境構築)
Grails/Vue.jsでTodoアプリ(CRUD
Grails/Vue.jsでTodoアプリ(認証) 1/2
Grails/Vue.jsでTodoアプリ(認証) 2/2
Grails/Vue.jsでTodoアプリ(Deploy as war)

初めに

昨日の記事でGrailsでRESTful APIサーバを構築、そしてフロントエンド用のVue.jsアプリケーションを作成しました。
今日は実際にGrailsとVue/axiosを連携させてToDoアプリケーションを作成していきます。

前提条件

Grailsで作成したアプリーションのディレクトリは今後$GRAILS_PROJECTとし、Vue.jsのアプリケーションのディレクトリはその直下なで$VUE_PROJECTと記述します。
私の環境では、$GRAILS_PROJECT/home/koji/work/grails/gvtodoで、$VUE_PROJECT$GRAILS_PROJECT/frontendになっています。

準備

GrailsとVueアプリケーションを起動しておいてください。それぞれ以下のコマンドで起動できます。

cd $GRAILS_PROJECT
grails
run-app
  • Vue
cd $VUE_PROJECT
yarn run dev

ページの作成

では、実際にフロントエンドの開発に入ります。 まず、$VUE_PROJECT/src/components/todo/Index.vueを作成し、中身を以下のようにします。

<template>
  <div>
    <h1>ToDoリスト</h1>
    <input v-model="title">
    <button @click="add">add</button>
    <ul>
      <li v-for="todo in todoList">
        <input
          class="todo-title"
          v-bind:value="todo.title"
          @keyup.enter="edit($event, todo.id)"
          @blur="edit($event, todo.id)">

        &nbsp;<button @click="remove(todo.id)">del</button>
      </li>
    </ul>
  </div>
</template>
<script>
  import axios from 'axios'

  let errorFunc = (error) => {
    alert(`HTTP Status: ${error.response.status}, [${error.response.data.message}]`)
  }

  const baseURL = 'http://localhost:8080/todo'

  export default {
    data: function () {
      return {
        title: '',
        todoList: []
      }
    },
    mounted: function () {
      this.list()
    },
    methods: {
      list: function () {
        axios.get(`${baseURL}`)
          .then((response) => {
            this.todoList = response.data
            console.log(this.todoList)
          }).catch(errorFunc)
      },
      add: function () {
        axios.post(`${baseURL}`, {
          title: this.title
        })
          .then((response) => {
            this.title = ''
            this.list()
          })
          .catch(errorFunc)
      },
      edit: function (event, id) {
        axios.put(`${baseURL}/${id}`, {
          title: event.target.value
        })
          .then((response) => {
            this.list()
          })
          .catch(errorFunc)
      },
      remove: function (id) {
        axios.delete(`${baseURL}/${id}`)
          .then((response) => {
            this.list()
          })
          .catch(errorFunc)
      }
    }
  }
</script>
<style>
  .todo-title {
    border: none;
  }
</style>

そして、上記のファイルにアクセスできるように、$VUE_PROJECT/src/router/index.jsを以下のようにします。

import Vue from 'vue'
import Router from 'vue-router'
import ToDoIndex from '@/components/todo/Index'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'ToDoIndex',
      component: ToDoIndex
    }
  ]
})

たったコレだけです!これでGrailsとVue.jsを使ったシンプルなToDoアプリケーションの完成です!
実際にhttp://localhost:8081/にアクセスして試してみてください。
なお、登録したToDoは、テキストをクリックすると編集できます。Enterを押下するか、フォーカスが外れた時点で自動的にサーバに更新リクエスト(PUT)を送ります。

今のところ全くGrailsが出てきませんでした。Grailsでいかにシンプルかつ高速にWebアプリケーションを構築できるかが分かりますね!

自分でRESTful API用のコントローラを作成する

さて、当然シンプルなAPIをいつまでも保てる保障はありません。やはりGrailsが自動で生成してくれるRESTful APIの動作を替えたい事もあるでしょう。
そこで今回は自分でRESTful API用のControllerを書いてみます。

コントローラの作成

とりあえず新しくItemコントローラを作成します。

grails> create-controller item

$GRAILS_PROJECT/grails-app/controllers/gvtodo/ItemController.groovyが生成されるので以下のようにします。

package gvtodo

import org.springframework.http.HttpStatus

class ItemController {

    static responseFormats = ['json', 'xml']

    /**
     * HTTPメソッド:GET、URL:/items/
     */
    def index() {
        respond(Todo.list())
    }

    /**
     * HTTPメソッド:POST、URL:/items/
     */
    def save(Todo todo) {
        todo.save(flush: true)
        respond(todo)
    }

    /**
     * HTTPメソッド:PUT、URL:/items/${id}
     */
    def update(Todo todo) {
        todo.save(flush: true)
        respond(todo)
    }

    /**
     * HTTPメソッド:DELETE、URL:/items/${id}
     */
    def delete(Todo todo) {
        respond([message: "You are not allowed to delete ToDo!"], status: HttpStatus.FORBIDDEN)
    }
}

これで独自のRESTful API用コントローラの完成です。
今回、Item(つまりToDo)の削除は禁止してみました。 delete(Todo todo)アクションがそれです。
このように、resopndの第2引数にステータスコードを渡してあげることで任意のレスポンスコードをクライアントに返すことが出来ます。

UrlMappingの修正

さて、自分で追加したControllerとActionをRESTful APIっぽいURL形式にするにはどうしたらいいでしょうか?
最後にIDが有る場合は更新か削除で...でもPUTとDELETEだと動作が違うし...
考慮することが結構あって面倒臭いですよね。しかし!Grials3のUrlMappingは当然そこの当たりの面倒臭さも包み隠してくれます! $GRAILS_PROJECT/grails-app/controllers/gvtodo/UrlMappings.groovyを以下のように修正します。
なお、このUrlMappings.groovyを編集した後はGrailsを再起動してください。

package gvtodo

class UrlMappings {

    static mappings = {

        // この1行を追加
        "/manual/items"(resources: 'item')
        
        "/$controller/$action?/$id?(.$format)?"{
            constraints {
                // apply constraints here
            }
        }

        "/"(view:"/index")
        "500"(view:'/error')
        "404"(view:'/notFound')
    }
}

"/manual/items"(resources: 'item')が意味するところは、resourcesで指定したControllerに対して、RESTful API形式でアクセスできる、ということになります。
今回であればItemコントローラはRESTful APIに対応していて、それには/manual/itemsでアクセスできる、ということになります。
このresourcesを利用することで、自作のControllerにRESTful API形式でアクセスできるようになります。
今回の例だと、

HTTPメソッド URI アクション
GET /items index()
POST /items save()
PUT /items/${id} update()
DELETE /items/${id} delete()

となります。

つまり、上記のアクション(メソッド)を持つコントローラを用意しておいて、それをUrlMappingのresourcesで指定することで、Grailsによって自動的にRESTful API形式でアクセスできるようになります。

この4つ以外にも、GSP用のcreate(), show(), edit()が有りますが、今回は利用しません。
このresourcesに関してはこの公式を参照してみてください。

新しいAPIを利用するようにする

後は、Vue.jsのアクセス先を切り替えるために、$VUE_PROJECT/src/components/todo/Index.vue内のbaseUrlを編集してください。

// const baseURL = 'http://localhost:8080/todo'
const baseURL = 'http://localhost:8080/manual/items'

これでhttp://localhost:8081/にアクセスすれば、以前と同じ用に普通にアプリケーションが使えることが確認できると思います。
また、削除もできなくなっているはずですので併せて確認してみてください。

まとめ

いかがだったでしょうか。私の長ったらしい説明は別として、Grails側がとても簡単にRESTful API対応の独自Controllerを作成できることが分かりますね。
明日はこのToDoアプリケーションに認証機構を追加します。