見出し画像

「Laravel + Vue.jsではじめる 実践 GraphQL入門」の全貌を大公開します!〜GraphQL + VueでSPAフロントエンドを開発!(アカウント作成ページ/機能の実装)編〜

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

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


前回からは、GraphQL + Vue でのSPAフロントエンド開発をお送りしております。

▼ 前回の記事はこちらをご覧ください💁🏿‍♂️


今回は、
どうぞご覧ください!



ApolloClientの設定をする

開発の土台が整いましたので、Apollo-clientの設定をしていきます。プロジェクトファイル内のmain.jsに記載していきます。


Apolloの仕様
Apollo-clientはreact-reduxを触っている人には、わかりやすいと思いますが 大元のProviderを作成し、親コンポーネントに読み込ませ、すべての子コンポーネントは親を参照できる仕組みです。

スクリーンショット 2019-11-14 16.25.31

Vueの初期化タイミングでvue-apolloライブラリ経由で、Apollo-clientを読み込ませ、子コンポーネントで、通 信処理を実装できるようになります。


Apolloプロバイダの設定をしていきます
main.jsを開きます。twitter-like-client/src/main.js


まずmain.js上部にライブラリ群をインポートします
• twitter-like-client/src/main.js

// apollo本体
import { ApolloClient } from "apollo-client";
// ApolloClientへのオプション
import { setContext } from "apollo-link-context";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
// vueとの紐付け
import VueApollo from "vue-apollo";


main.jsの中部にapollo-clientとオプションの紐付けをしていきます。
コメントアウトにスクリプトに対する説明をしています。

// vueでvue-apolloを使用する事の紐付け
Vue.use(VueApollo);
// createHttpLinkでエンドポイントの指定をします
const httpLink = createHttpLink({
  uri: "http://localhost:8000/graphql"
});
// ログイン後にBearerを指定したJWT通信が入るので先に記述します
// 今回はlocalstorageを用いてtokenを保持します
const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem("vue_token");
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    } 
  }
});
// Apolloclientの初期化です
// linkとmemoryCacheを有効にします
const apolloClient = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
});
// apolloProviderを設定します。
// あとでstoreにエラーを通知しようと思うのでstoreへのコミットを記述します
const apolloProvider = new VueApollo({
  defaultClient: apolloClient,
  errorHandler(error) {
    store.commit("error");
  }
});

// 最後にVueの初期化の際にapolloproviderを設定します
new Vue({
  router,
  store,
  apolloProvider,
  render: h => h(App)
}).$mount('#app')


記載内容の全て

import Vue from 'vue'
import './plugins/vuetify'
import App from './App.vue'
import router from './router'
import store from './store'

// apollo本体
import { ApolloClient } from "apollo-client";
// ApolloClientへのオプション
import { setContext } from "apollo-link-context";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
// vueとの紐付け
import VueApollo from "vue-apollo";

Vue.config.productionTip = false

// vueでvue-apolloを使用する事の紐付け
Vue.use(VueApollo);
// createHttpLinkでエンドポイントの指定をします

const httpLink = createHttpLink({
  uri: "http://localhost:8000/graphql"
});
// ログイン後にBearerを指定したJWT通信が入るので先に記述します
// 今回はlocalstorageを用いてtokenを保持します
const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem("vue_token");
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  }
});
// Apolloclientの初期化です
// linkとmemoryCacheを有効にします
const apolloClient = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
});
// apolloProviderを設定します。
// あとでstoreにエラーを通知しようと思うのでstoreへのコミットを記述します
const apolloProvider = new VueApollo({
  defaultClient: apolloClient,
  errorHandler(error) {
    store.commit("error");
  }
});

// 最後にVueの初期化の際にapolloproviderを設定します
new Vue({
  router,
  store,
  apolloProvider,
  render: h => h(App)
}).$mount('#app')


Apolloを利用する準備が整いました
ここからサーバーサイドで実装したAPIを使い、アプリケーション作成に入ります。


アカウント作成ページ/機能の実装

ページ・機能の作成は大きく以下の手順で行なっていきます

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

それでははじめましょう!


アカウント作成機能を実装する

viewsフォルダにSignup.vueを作成します

スクリーンショット 2019-11-14 16.42.19

内容は以下のように記載しておきます

<template>
  <div>アカウント作成ページ</div>
</template>


router.jsに追記
routesの配列にsignupのルートを追加
• 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' // ここに追記

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
   }, 
  ]
})


localhost:8080/signupにアクセス
Signup.vueに記載した内容が表示されていればOKです。

スクリーンショット 2019-11-14 16.47.27


フォーム入力とsubmitイベントを記載していく
Signup.Vueを修正
vプレフィックスが付いているタグが Vuetify のタグです。

<v-form @submit="createAccount"onSubmit="return false;">


今回、ボタンのトリガーで実行するメソッド
createAccount
と、submit時にリロードしてしまう仕様を止めるため onSubmit="return false;" を記載しています。


tampleteタグ部分
• twitter-like-client/src/views/Signup.vue

<template>
  <v-form @submit="createAccount" onSubmit="return false;">
    <v-container>
      <v-layout>
        <v-flex xs12 md4>
          <v-text-field
            v-model="name"
            label="name"
            required
          ></v-text-field>
          <v-text-field
            v-model="twitterId"
            label="twitterId"
            required
          ></v-text-field>
          <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-text-field
            v-model="passConf"
            label="password_confirm"
            required
          ></v-text-field>
          <v-btn color="primary" type="submit">アカウント作成</v-btn>
        </v-flex>

      </v-layout>
    </v-container>
  </v-form>
</template>


GraphQLを記述するJSファイルの作成
GraphQLを記述するJSファイル作成していきます。
srcフォルダ直下にgraphqlフォルダを作成しました。

スクリーンショット 2019-11-14 17.29.05

今回は、graphqlフォルダ内にmutation.js / query.jsを配置しました。


mutation.jsにGraphQLを記述します
graphql-tagライブラリを使用すると、JSのテンプレートリテラル形式でGraphQLを記載できます。
サーバーサイドで定義した、CreateAccountのmutationを記述します。
フロントエンドの場合、mutationで受け付けるvariablesの値は'mutaion(この中)'で受け付けます。 引数のような扱いで $ に変数としてvariablesを定 義し実際のCreateAccountの内部で変数を付与していきます。

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

import gql from 'graphql-tag';

export const CREATE_ACCOUNT = gql`
  mutation(
    $name: String!
    $twitter_id: String!
    $email: String!
    $password: String! $password_confirmation: String!
  ) {
    CreateAccount(
      name: $name
      twitter_id: $twitter_id
      email: $email
      password: $password
      password_confirmation: $password_confirmation
   ) {
      account {
        twitter_id
      }
      token {
        access_token
        token_type
        expires_in
      } 
   }
 } 
`;


Signup.vueにロジックを記載していきます

Signup.vueのdata作成
scriptタグをtemplateタグの下に記述していきます。
vuetifyのフォーム記述に則り、dataに対して、フォームのバリューを記載しています。※バリデーションを設定する事もできます
また、mutation.jsもインポートしておきましょう。

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

~
</template>
<script>
  import { CREATE_ACCOUNT } from "../graphql/mutation.js";

  export default {
    data: () => ({
      name: "",
      twitterId: "",
      email: "",
      passConf: "",
      pass: ""
    })
  }
</script>


script部分にcreateAccountが実行された時のロジックを書きます

ポイントとなるのは this.$apollo です!
Providerに設定して親のVueに設定したApolloクライアントは子のコンポーネントで this.$apollo という形で参照できます。

mutationの実行は this.$apollo.mutate() で実行します!
this.$apollo.mutateはオブジェクトを引数で取り、GraphQLの記述やvariablesを記載します。
以下のコードを 見るとお分かりかと思いますが、Promise形式でthenの中で通信後の記述が可能です。

~
</template>
<script>
  import { CREATE_ACCOUNT } from "../graphql/mutation.js";

  export default {
    data: () => ({
      name: "",
      twitterId: "",
      email: "",
      passConf: "",
      pass: ""
    }),
    methods: {
      createAccount(e){
        // mutation
        this.$apollo.mutate({
          // GraphQL
          mutation: CREATE_ACCOUNT,
          // Variables
          variables: {
            name: this.name,
            twitter_id: this.twitterId,
            email: this.email,
            password: this.pass,
            password_confirmation: this.passConf,
          },
        }).then((data) => {
            // Sucess
           console.log(data);
        }).catch((error) => {
           // Error
           console.error(error)
         });
       }
     }
   }
</script>


Signup通信の準備ができたので実際に通信をしてみましょう
通信の際のチェックポイント
- Laravelのバックエンドサーバーは起動していますか?
- main.jsで記載したエンドポイントは正しいですか?

必要な入力項目を記載

スクリーンショット 2019-11-14 17.52.21


ボタンを押してgraphQLが叩かれているか確認
画像のように、Devツールのnetworkにgraphqlの通信があれば成功です

スクリーンショット 2019-11-14 17.53.11


エラーが出てしまった場合
筆者が経験した通信時のエラーなどになります。
以下チェックしてみると解決されるかもしれません。

原因となる事:
- エンドポイントが間違っている
- バックエンドに適切なクロスオリジン対応がなされていない
- バックエンド側のバリデーションに引っかかってる
- graphQLの記載 or variablesが間違っている or タイポがある
など


通信に成功していたらtokenをlocalstorageに保存してstoreにロ グイン状態を保存します
今回Vuexでの状態管理は、ログイン状態管理のみ行います。


store.jsにログイン状態を保持するためのスクリプトを記載します

Vuexの記述にも mutations がありますがこれはGraphQLとは関係ありません。 mutationsに記載されたメソッドは、あくまでstore側に状態を伝えた際に実行されるメソッド群となります。
今回 loginederror のメソッドを記述してますが、view側でログインした場合にstoreに対して store.commit ("logined") という形で状態を伝えます。 そうすると、下記に記載した logined メソッドが発火します。

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

import Vue from "vue";
import Vuex from "vuex";
import router from "./router";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    logined: localStorage.getItem("vue_token") ? true : false,
    error: false
  },
  mutations: {
    logined(state, payload) {
      state.logined = true;
    },
    error(state, payload) {
      // main.jsに記載されたGraphQLのグローバルエラーをハンドリングする
      router.push("/login");
    } 
  }
});


Signup.vueの通信成功時にtopページを表示されようにする

まずはSignup.vueでログイン成功をvuexに伝え、ページ切り替え処理を
入れる
まずは、 store.js をインポートします。 そして、 .then((data) => { からの記述が通信成功時の処理ですので、そこに

const token = localStorage.setItem("vue_token", data.data.CreateAccount.token.access_to ken);
 store.commit("logined");
 this.$router.push("/");
- ローカルストレージへの保存
- ストアへの状態送信
- ルーターへのページ切り替え処理

を記載しています。
• twitter-like-client/src/views/Signup.vue

<script>
  import { CREATE_ACCOUNT } from "../graphql/mutation.js";
  import store from "../store.js"; // storeをインポート

  export default {
    data: () => ({
      name: "",
      twitterId: "",
      email: "",
      passConf: "",
      pass: ""
    }),
    methods: {
      createAccount(e){
        //mutation
        this.$apollo.mutate({
          // GraphQL
          mutation: CREATE_ACCOUNT,
          // Variables
          variables: {
            name: this.name,
            twitter_id: this.twitterId,
            email: this.email,
            password: this.pass, password_confirmation: this.passConf,
          },
        }).then((data) => {
           // Sucess
           // tokenの保存および、storeへ状態を送信、ページの切り替え
          console.log(data);
          const token = localStorage.setItem("vue_token", data.data.CreateAccount.token.
access_token);
          store.commit("logined");
          this.$router.push("/"); }).catch((error) => {
          // Error
          console.error(error)
        });
      } 
    }
  } 
</script>


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

<template>
  <v-form @submit="createAccount" onSubmit="return false;">
    <v-container>
      <v-layout>
        <v-flex xs12 md4>
          <v-text-field
            v-model="name"
            label="name"
            required
          ></v-text-field>
          <v-text-field
            v-model="twitterId"
            label="twitterId"
            required
          ></v-text-field>
          <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-text-field
            v-model="passConf"
            label="password_confirm"
            required
          ></v-text-field>
          <v-btn color="primary" type="submit">アカウント作成</v-btn>
        </v-flex>

      </v-layout>
    </v-container>
  </v-form>
</template>

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

  export default {
    data: () => ({
      name: "",
      twitterId: "",
      email: "",
      passConf: "",
      pass: ""
    }),
    methods: {
      createAccount(e){
        //mutation
        this.$apollo.mutate({
          // GraphQL
          mutation: CREATE_ACCOUNT,
          // Variables
         variables: {
          name: this.name,
          twitter_id: this.twitterId,
          email: this.email,
          password: this.pass, password_confirmation: this.passConf,
        },
      }).then((data) => {
          // Sucess
         console.log(data);
         const token = localStorage.setItem("vue_token", data.data.CreateAccount.token.
access_token);
         store.commit("logined");
         this.$router.push("/");
       }).catch((error) => {
          // Error
          console.error(error)
        });
      } 
    }
  } 
</script>



コラム

VuexのstoreでGraphQL通信処理の記述はしないの?
Vuexを使用したVueアプリケーションの開発ではVuex側(store.jsなど)で通信処理を書くことが多いと思います。

今回は、 vue-apollo を利用した開発という事で、コンポーネントでの通信を中心に解説できればと思い、Vuex側でGraphQL通信をすることはやめました。

Store側でのGraphQL通信をする場合、Vue-apolloを使わなくてもaxiosで十分という理由もあります。大規模な開発をしていく場合やすでにVuexを使用したプロジェクトの場合などは Vuexのstore側で通信した方が開発しやすいかもしれません。



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

いかがでしたでしょうか?
13000字近い内容をお読みいただき、ありがとうございます!

次回も木曜日に、公開いたします!
ぜひ見ながら、実際に手を動かしていただけるとなおうれしいです!

引き続きご覧くださいませ。


Fin.

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

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

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


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