Amplifyフレームワークを使ってGraphQL APIを叩く

##概要
この記事ではAmplifyフレームワークを使ってGraphQL APIにアクセスする方法を紹介します。
GraphQLのスキーマは用意されている前提なので、主にフロントエンドからバックエンドとのつなぎ込みをする際の具体的なコードを列挙します。

##実践
amplify CLIでAPIを構築すると、queries.js、mutations.js、subscriptions.jsの３つのファイルが作成されます。
各query,mutation,subscriptionはモジュールとしてexportされているので、呼び出し元のファイルから必要なものをimportします。
そのままimportすると全てのフィールドが含まれているので、必要なフィールドだけを取得したい場合はフィールドを削ったり、別ファイルに書き出しても構いません。
以下の例ではNuxtプロジェクトを想定し、ストアファイルの中からGraphQLの各オペレーションを実行していますが、VueのApp.vueからでもReactのApp.jsからでも基本的なgraphqlメソッドの書き方は同じです。

```store/index.js
import { API, graphqlOperation } from 'aws-amplify';
import { createTodo } from './graphql/mutations';
import { listTodos } from './graphql/queries';
import { onCreateTodo } from './graphql/subscriptions';

export const state = () => ({
  todos:[] ,
  subscription: null
})

export const mutations = {
 /**
   * 取得したtodoデータをstateにセット
   */
  setTodos(state, Todo) {
    state.todos = todo
  },
 /**
   * サブスクリプションをセット
   */
  setSubscription(state, subscription) {
    state.subscription = subscription
  },
}

export const actions = {
  /**
   * Todo一覧を取得
   */
  async listTodos({ state, commit }) {
    try {
      const { data } = await API.graphql(graphqlOperation(listTodos))
      commit('setTodos', data.listTodos.items)
    } catch (e) {
      console.error(e)
    }
  }
  /**
   * Todoを作成
   */
  async createTodo(context, { todo }) {
    try {
      await API.graphql(
        graphqlOperation(createTodo, {
          input:{
            name: todo.name,
            description: todo.description,
            priority: todo.priority
          }
        })
      )
    } catch (e) {
      console.error(e)
    }
  },
  /**
   * Todoの作成を購読
   */
  async subscribe({ state, commit }) {
    const subscription = await API.graphql(graphqlOperation(onCreateTodo)).subscribe({
      next: eventData => {
        const todo = eventData.value.data.onCreateTodo
        commit('updateTodos', [...state.todos, todo])
      }
    })
    commit('setSubscription', subscription)
  },
  /**
   * Todo作成の購読解除
   */
  unsubscribe({ state }) {
    if (!state.subscription) return
    state.subscription.unsubscribe()
  },

```
まずはファイル先頭で必要なモジュールをインポートします。

```js
import { API, graphqlOperation } from 'aws-amplify';
import { createTodo } from './graphql/mutations';
import { listTodos } from './graphql/queries';
import { onCreateTodo } from './graphql/subscriptions';
```
graphqlメソッドを以下のように書く場合は、APIのインポートだけでも済みますが、graphqlOperationを使うとよりすっきり書けるので両方インポートします。

```js

  await API.graphql({
        query: createTodo,
        variables: {input: todo},
      });
```

バンドルサイズを小さくするため、以下のように特定のカテゴリ(この場合api)のみインポートすることも可能です。

```js
import API from '@aws-amplify/api';
```
GraphQLのAPI呼び出しは、await API.graphql(graphqlOperation(クエリ, パラメータ))のように記述します。

```js
const { data } = await API.graphql(graphqlOperation(listTodos))
      commit('setTodos', data.listTodos.items)
```
戻り値をdataという定数に代入し、オブジェクトの中から必要なデータを取り出して'setTodos'というmutation(vuex)に渡しています。
この場合itemsにはTodoの配列が格納されていることを想定しています。

```js
await API.graphql(
        graphqlOperation(createTodo, {
          input: {
            name: todo.name,
            description: todo.description,
            priority: todo.priority
            due: todo.due
          }
        })
      )
```
Todo作成のミューテーションにはパラメータを渡しています。
パラメータはオブジェクトの形式で記述し、inputプロパティの値には複数のプロパティを持ったオブジェクトが格納されています。
createTodoという関数にinputという変数名のオブジェクトを渡しているイメージです。
以下はmutations.jsファイル内のクエリ言語です。

```graphql/mutations.js
export const createTodo = /* GraphQL */ `
  mutation CreateTodo($input: CreateTodoInput!) {
    createTodo(input: $input) {
      id
      name
      description
      priority
      due
    }
  }
`;
```
この＄input変数に先ほどのinputの値が入ります。
ちなみにinputを変数に格納したオブジェクトとして渡さず、

```js
await API.graphql(
        graphqlOperation(createTodo, {
          name: todo.name,
          description: todo.description
        })
      )
```
このように書いた場合、スキーマ言語側も書き換える必要があります。

```js
export const createTodo = /* GraphQL */ `
  mutation CreateTodo($input: CreateTodoInput!) {
    createTodo(input: {name:$name, description:$description}) {
      id
      name
      description
      priority
      due
    }
  }
`;
```
この例では＄nameと$descriptionの二つの変数でそれぞれ値を受け取っています。
こちらの方が冗長にも思えますが、スキーマ言語側で受け取る値を明示的にしたい場合などにこちらの書き方をすることがあります。

todoを新規に作成したのですから、作成されたものをすぐさま表示したいはずですが、上述のストアファイル内ではcreateTodoの戻り値を定数などで受け取っておらず、mutation(vuex)も呼び出していません。
これはsubscriptionでtodoの作成イベントを購読しているためです。

```js
const subscription = await API.graphql(graphqlOperation(onCreateTodo)).subscribe({
      next: eventData => {
        const todo = eventData.value.data.onCreateTodo
        commit('updateTodos', [...state.todos, todo])
      }
    })
    commit('setSubscription', subscription)
```
途中まではqueryやmutationと書き方は同じですが、API.graphql()の後にメソッドチェーンでsubscribe()と続いています。
その中でさらにnext:と続いており、アロー関数の中で受け取ったデータを利用した処理が書かれています。 
受け取ったデータというのはonCreateTodoの発火時に返ってくる選択セットに指定したフィールドのデータです。
ここではeventDataという名前でデータを受け取って、value→data→onCreateTodoとネストしていくとようやくcreateTodoされたtodoのデータが取得できています。
これをmutation(vuex)に渡せばstateのtodosを更新してくれるので、createTodoではcommitしていなかった（する必要がなかった）というわけです。
それではconst subscriptionで受け取っている値とはなんなのでしょうか。
これはイベントの購読を停止する(unsubscribeする)ために利用する、Subscriptionインターフェースを受け取っています。
それをstateに保存しておくことで、任意のタイミングで購読停止処理(unsubscribe)を呼び出せるのです。

```js
unsubscribe({ state }) {
    if (!state.subscription) return
    state.subscription.unsubscribe()
  }
```
このunsubscribeを行わないと、subscribeしてから延々とイベントの待ち受け状態になってしまうので、インスタンス削除時(Nuxtで言えばdestroyedフック)などのタイミングでこの処理を呼び出します。

##まとめ
API.graphql()メソッドでシンプルにバックエンド接続できるAmplifyフレームワークの紹介でした。
今回は基本的な内容にとどまっているので、今後filterによる絞り込みや、AppSyncSDKを使った書き方、S３との接続による画像送信処理などについても紹介できればと思っています。