スクリーンショット_2019-11-28_18

「Laravel + Vue.jsではじめる 実践 GraphQL入門」の全貌を大公開します!〜GraphQL + VueでSPAフロントエンドを開発!(ログインページ/機能の実装〜残りのAPI機能/実装について)編〜

こんにちは。kzkohashi です。
FISM という会社でCTOをやっております。

今年4月に「Laravel + Vue.jsではじめる 実践 GraphQL入門」という技術書籍を出版しました。


本記事では、GraphQL + Vue でのSPAフロントエンド開発について、書籍の内容をそのまま公開しております。


▼マガジンで更新中!


▼ Wantedlyでダイジェストまとめています!


今回、なんと最終章!
「GraphQL + VueでSPAフロントエンドを開発!(ログインページ/機能の実装〜残りのAPI機能/実装について)編」です!

どうぞご覧ください!


✂︎ ---------------------

ログインページ/機能の実装

続いてログインページを作成します
ここまで結構ボリュームがあったかと思いますが、フォーム入力・mutationが終わったので後は、

- query通信
- 通信後のviewアップデート

この2点ができればほぼできる事のすべてとなります。
それでは、ログインページを作成していきます。サインアップページ同様

- views フォルダに新しくページ用のvueファイルを作成
- routerにurlとページ用のvueの紐付けを記述
- ページ用vueファイルにDOMを記載
- GraphQLを記述
- ページ用vueファイルにロジックを記載

となります。


viewsフォルダにLogin.vueを作成

スクリーンショット 2019-11-28 14.26.05

signup同様にフォームを記述していきます。

• twitter-like-client/src/views/Login.vue

<template>
  <v-form @submit="login" onSubmit="return false;">
    <v-container>
      <v-layout>

        <v-flex xs12 md4>
          <v-text-field
            v-model="email"
            label="E-mail"
            required
          ></v-text-field>
          <v-text-field
            v-model="pass"
            label="password"
            required
          ></v-text-field>
          <v-btn color="primary" type="submit">login</v-btn>
         <router-link to="/signup"><v-btn flat>signup</v-btn></router-link>
       </v-flex>
 
     </v-layout>
    </v-container>
  </v-form>
</template>


router.jsに追記
ログインページを追加しました

• twitter-like-client/src/router.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import Signup from './views/Signup.vue'
import Login from "./views/Login.vue";

Vue.use(Router)

export default new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/", 
      name: "home",
      component: Home
    },
    {
      path: "/signup",
      name: "signup",
      component: Signup
    }, 
    {
      path: " /login",
      name: "login",
      component: Login
    } 
  ]
});


ログインページにアクセス

http://localhost:8080/login このような表示がされていれば成功です

スクリーンショット 2019-11-28 14.36.55


ログインのGraphQLを記述
先ほど同様にmutation.jsに追記します

• twitter-like-client/src/graphql/mutation.js

// ログイン
export const LOGIN = gql`
  mutation($email: String! $password: String!) {
    Login(
      email: $email
      password: $password
     ){
      access_token
      expires_in
     }
   }
`;


ログインの通信ロジックを記述
ここもSignupと同様、フォームの入力値をvariablesにいれてmutateメソッドを叩きます。通信後の処理も同じです。
• twitter-like-client/src/views/Login.vue

<script>
  import { LOGIN } from "../graphql/mutation.js";
  import store from "../store.js";

  export default {
    data: () => ({
      email: "",
      pass: ""
    }),
    methods: {
      login(e){
        // ミューテーション
        this.$apollo.mutate({
          // Query mutation: LOGIN,
          // Parameters
          variables: {
            email: this.email,
            password: this.pass
          },
        }).then((data) => {
          const token = localStorage.setItem('vue_token', data.data.Login.access_token);
          store.commit("logined");
          this.$router.push("/");
        });
      }
    } 
  }
</script>


ログインして見る
ログイン後TOPページにいくように設定していますので、Vuetifyのtopページが出れば成功です。

スクリーンショット 2019-11-28 14.48.31


これでLogin.vueの記述は終わりです。すべてのスクリプトはこちらです

• twitter-like-client/src/views/Login.vue

<template>
  <v-form @submit="login" onSubmit="return false;">
    <v-container>
      <v-layout>

        <v-flex xs12 md4>
          <v-text-field
            v-model="email"
            label="E-mail"
            required
          ></v-text-field>
          <v-text-field
            v-model="pass"
            label="password"
            required
          ></v-text-field>
          <v-btn color="primary" type="submit">login</v-btn>
          <router-link to="/signup"><v-btn flat>signup</v-btn></router-link>
        </v-flex>

       </v-layout>
     </v-container>
   </v-form>
</template>
<script>
   import { LOGIN } from "../graphql/mutation.js";
   import store from "../store.js";

   export default {
     data: () => ({
       email: "",
       pass: ""
     }),
     methods: {
       login(e){
         // ミューテーション
         this.$apollo.mutate({
           // Query
           mutation: LOGIN,
           // Parameters
           variables: {
             email: this.email,
             password: this.pass
           },
         }).then((data) => {
           const token = localStorage.setItem('vue_token', data.data.Login.access_token);
           store.commit("logined");
           this.$router.push("/");
         }); 
      }
    }
  }
</script>



ツイート/タイムラインページ/機能の実装

続いて、いよいよツイート/タイムライン表示の画面を作ります
ツイート、タイムライン表示はトップページがいいので、すでにrouterで定義されているHome.vueを修正していきたいと思います。

まずはツイート送信をする
慣れてきていると思いますので、 先にmutationを書き、DOMとロジック両方を同時に記述します。 mutation.jsに以下を追記

• twitter-like-client/src/graphql/mutation.js

export const CREATE_TWEET = gql`
  mutation($tweet: String!) {
    CreateTweet(content: $tweet) {
      id
      content
      tweeted_at
    }
  } 
`;


Home.vue
すでにHome.vueで定義されているコードはすべて消して下記を記述します。

• twitter-like-client/src/views/Home.vue

<template>
    <v-container>
      <v-layout row wrap>
        <v-flex xs12>
          <v-subheader>
            Tweet
          </v-subheader>
        </v-flex>
        <v-flex mb-2>
          <v-sheet class="pa-4" elevation=6>
            <v-form @submit="postTweet" onSubmit="return false;">
              <v-text-field
                v-model="tweet"
                label="tweet"
                required
              ></v-text-field>
              <v-btn type="submit" color="primary">ツイート</v-btn>
            </v-form>
          </v-sheet>
        </v-flex>
      </v-layout>
    </v-container>
</template>
<script>
  import { CREATE_TWEET } from "../graphql/mutation.js";

  export default {
    data: () => ({
      tweet: ""
    }),
    methods: {
      postTweet(e){
        this.$apollo.mutate({
          mutation: CREATE_TWEET,
          variables: {
            tweet: this.tweet,
          }
        }); 
      }
    } 
  }
</script>


通信を確認
表示上特に代わりが無いのですが、devツール上でツイートが送信されているのが確認できると思います

スクリーンショット 2019-11-28 15.35.25


タイムラインを作成する
今までmutationでの通信をしてきたのですが、queryでの通信が始まります。 Vue-apolloでの記述方法も変わるので注意してみていただければと思います。

まずはquery通信のみ確認する
いきなり、query通信とDOM描画を同時に説明するとわかりづらいかと思いますので、先に通信から始めます。

query.jsにGraphQLを記載

• twitter-like-client/src/graphql/query.js

import gql from 'graphql-tag';
export const TIMELINE = gql`
  query($id: Int!) {
  Timeline(id: $id) {
    id
    tweet {
      id
      content 
      account {
        twitter_id
        avatar
      }
        }
      originalFavorite {
        account {
          twitter_id
          name
        } 
      }
      favorite { 
        favorite_at
      } 
    }
  } 
`;


Home.vueにquery通信を書きます
Home.vueのscriptを下記に書き換えます。
mutateとの違いは、export defaultのオブジェクトにapolloというプロパティを作ります。

• twitter-like-client/src/views/Home.vue

<script>
  import { CREATE_TWEET } from "../graphql/mutation.js";
  import { TIMELINE } from "../graphql/query.js";

  export default {
    data: () => ({
      tweet: "",
      timelines: [],
    }),
    methods: {
      postTweet(e){
        this.$apollo.mutate({
          mutation: CREATE_TWEET,
          variables: {
            tweet: this.tweet,
          }
        }); 
      }
    },
    apollo: {
      timelines: {
        query: TIMELINE,
        loadingKey: 'loading',
        // Parameters
        variables () {
          return {
            id: 0
          } 
        },
        update (data) {
          return data.Timeline;
        },    
      }
    }
  }
</script>


ポイント1:dataとのリンク
export defaultオブジェクトに、queryの内容を書いていくのですが、ポイントとして、上で定義してる timelines:[] という変数とapolloオブジェクト内に記載した、 timelines:{ というプロパティはリンク しています。
※ここが違うとエラーが出てしまうので、気をつけてください。

ポイント2:updateメソッドでdataに代入
updateメソッドは無くても、dataのtimelinesに代入されますが、 配列のみ取得したい時などはupdateメソッドからデータをreturnする事で、 必要なデータのみ返却できます。

update (data) {
  return data.Timeline;
},


通信の確認
上記の記述でqueryは通信されます。 devtool上で通信がされているか確認してみましょう。

スクリーンショット 2019-11-28 15.54.25


ツイートの表示
今回、ツイートを表示するに伴い、Vueの子コンポーネントに表示しようと思います。 まずは、componentsデ ィレクトリにTimeline.vueを作成しましょう。

スクリーンショット 2019-11-28 15.57.34


Timeline.vueにコードを記載
ここは基本的なvueのコンポーネント作成となります。

• twitter-like-client/src/components/Timeline.vue

<template>
  <v-list two-line>
      <template v-for="(timeline, index) in timelines">
        <v-list-tile
          :key="index"
          avatar
        >
          <v-list-tile-avatar>
            <img :src="'http://localhost:8000/storage/images/' + timeline.tweet.account.
avatar" v-if="timeline.tweet.account.avatar"/>
            <v-icon v-else>account_circle</v-icon>
          </v-list-tile-avatar>

          <v-list-tile-content>
            <div v-text="timeline.originalFavorite ? timeline.originalFavorite.account.n
ame + 'さんがいいねしました' : ''"></div>
            <v-list-tile-sub-title v-html="timeline.tweet.account.twitter_id"></v-list-tile-sub-title>
            <v-list-tile-title v-html="timeline.tweet.content"></v-list-tile-title>
          </v-list-tile-content>

        </v-list-tile>
        <v-divider
          :key="index+'_divider'"
        ></v-divider>
      </template>
     </v-list>
</template>


<script>
  export default {
    props: {
      timelines: Array
    }
  }
</script>


Home.vueにTimelineコンポーネントを入れ込む
Home.vueを以下のコードに書き換え 変更点は、Timelineをインポートして、export default内のコンポーネン トプロパティに登録 TimelineタグをDOMに配置して、Timelineデータを:v-bindディレクティブで紐付けしてい ます。

• twitter-like-client/src/views/Home.vue

<template>
    <v-container>
      <v-layout row wrap>
        <v-flex xs12>
          <v-subheader>
            Tweet
          </v-subheader>
         </v-flex>
         <v-flex mb-2>
           <v-sheet class="pa-4" elevation=6>
             <v-form @submit="postTweet" onSubmit="return false;">
               <v-text-field
                 v-model="tweet"
                 label="tweet"
                 required
               ></v-text-field>
               <v-btn type="submit" color="primary">ツイート</v-btn>
             </v-form>
            </v-sheet>
           </v-flex>

           <!-- タイムラインコンポーネント -->
           <v-flex xs12>
             <v-sheet class="pa-4" elevation=6>
               <Timeline v-bind:timelines="timelines" />
             </v-sheet>
           </v-flex>
         </v-layout>
       </v-container>
</template>

<script>
import { CREATE_TWEET } from "../graphql/mutation.js";
import { TIMELINE } from "../graphql/query.js";
import Timeline from "../components/Timeline.vue";

export defcalut {
  components: {Timeline},
  data: () => ({
    tweet: "",
    timelines: [],
  }),
  methods: {
    postTweet(e){
      this.$apollo.mutate({
        mutation: CREATE_TWEET,
        variables: {
        tweet: this.tweet,
      }
   });
  }
 },
 apollo: {
   timelines: {
      query: TIMELINE,
      loadingKey: 'loading',
      // Parameters
      variables () {
        return {
          id: 0
        }
      },
      update (data) {
        return data.Timeline;
      }, 
    }
  } 
}
</script>


表示の確認
以下のような表示になっていれば成功です。

スクリーンショット 2019-11-28 17.50.59


ツイートをしたらタイムラインを更新する
大分ツイッターらしくなってきたかと思います。 ただ、ここまでだとツイートをしてもタイムラインは更新さ れないので、 ツイート送信が完了したらタイムラインを再度更新するようコードを書き換えます。

Home.vueのpostTweetメソッドにthenをつけて、再度GraphQLを叩くようにします。
this.$apollo はmutateメソッドだけでなくqueryも叩けるようメソッドが用意されています。 今回はrefetchメソッドをタイムラインを最新にします。

• twitter-like-client/src/views/Home.vue

postTweet(e){
  this.$apollo.mutate({
    mutation: CREATE_TWEET,
    variables: {
      tweet: this.tweet,
    }
  }).then((data) => { // thenを追加
    this.$apollo.queries.timelines.refetch({
      id: 0
    });
  });


リストが更新されるようになっていれば成功です。

スクリーンショット 2019-11-28 17.54.09


残りのAPI機能/実装について

残りのAPI機能/実装に関しては、ここまでご説明した以下の作成手順で問題なく実装が可能です。

- views フォルダに新しくページ用のvueファイルを作成
- routerにurlとページ用のvueの紐付けを記述
- ページ用vueファイルにDOMを記載
- GraphQLを記述
- ページ用vueファイルにロジックを記載
- 実行!


他のAPIに関しても実装を見たい方へ
残りのAPI実装に関しても、私の方で実装したサンプルがございますので、 githubのURLをご確認いただければと思います。


✂︎ ---------------------

いかがでしたでしょうか?
最後までお読みいただき、ありがとうございます!

本連載はこれにて一旦おしまいになりますが、直近だと 1/23 にGraphQLのイベントも開催予定となっております。

今後とも大橋とFISMをよろしくお願い致します!

 

Fin.


▼ Twitterもやってます。よければフォローもお願いします🙇🏿‍♂️

▼ FISM社についてはこちら💁🏿‍♂️​

▼ 現在Wantedlyにて開発メンバー募集中です!GraphQL + Laravel + Vue.js + Swift で開発しております👨🏿‍💻まずはお気軽にお話ししましょう🙋🏿‍♂️


この記事が気に入ったらサポートをしてみませんか?