AWS AmplifyをVue.jsで使い倒したい！（API編）

# はじめに
[先日書いた記事](https://qiita.com/jre233kei/items/d719159c6b0b44b3980d)の続きです。

Amplifyでは、GraphQLを使って、APIを追加する事ができます。

# GraphQLとは
GraphQLを使うと従来のRESTfulなAPI設計とは違って、過不足なく必要なデータを取ってくる事ができます。JSONをやり取りする感覚に近く、とても直感的です。

Amplifyでは、GraphQLでデータの構造を定義するとそれに伴い

- データの追加
- データの更新
- データの取得
- データの削除

を行う関数を自動で作成してくれます。便利ですね。

# セットアップ

コンソール上で次のコマンドを実行します。

```
$ amplify add api
```

指示にしたがって、設定を行います。

<img width="766" alt="スクリーンショット 2020-01-28 23.15.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163480/235b0e24-a4c4-0ae4-cf5f-69c34b8fe716.png">

Authorization typeを設定していきます。
今回は、Amazon Cognito User Poolを選択しました。

各方式の特徴は[こちら](https://qiita.com/jre233kei/items/cc4c0ca8e05bf1535ddd)にまとめています。

```graphql:schema.graphql
type Todo @model {
  id: ID!
  name: String!
  description: String
}
```

今回は、標準のTodoをそのまま使っていきます。
!マークがついた場合、**nullを許さない**という意味です。つまり必須項目ですね。

次に、GraphQLのテーブルをリモートで作成していきます。

```
amplify push
```

```
✔ Successfully pulled backend environment develop from the cloud.

Current Environment: develop

| Category | Resource name         | Operation | Provider plugin   |
| -------- | --------------------- | --------- | ----------------- |
| Api      | amplifysample         | Create    | awscloudformation |
| Auth     | amplifysample******** | No Change | awscloudformation |
```
作成の準備ができているようですね。
他の項目は全てデフォルトのままにして進めていきます。

クラウドへの反映に時間がかかりますが、そのまま待ちましょう。

作成が完了すると、GraphQLのエンドポイントが表示されるので、メモしておきましょう。

```
GraphQL endpoint: https://*******.appsync-api.us-east-1.amazonaws.com/graphql
```
このhttps://から始まるURLが、GraphQLの操作の起点になるので、メモしておきましょう。


続いて、GraphQLを操作するための関数が自動的に作成されます。早速確認していきましょう。

`src/graphql`を見てみましょう。


まずは`mutation.js`を確認します。

```javascript:mutation.js
/* eslint-disable */
// this is an auto generated file. This will be overwritten

export const createTodo = `mutation CreateTodo(
  $input: CreateTodoInput!
  $condition: ModelTodoConditionInput
) {
  createTodo(input: $input, condition: $condition) {
    id
    name
    description
  }
}
`;
export const updateTodo = `mutation UpdateTodo(
  $input: UpdateTodoInput!
  $condition: ModelTodoConditionInput
) {
  updateTodo(input: $input, condition: $condition) {
    id
    name
    description
  }
}
`;
export const deleteTodo = `mutation DeleteTodo(
  $input: DeleteTodoInput!
  $condition: ModelTodoConditionInput
) {
  deleteTodo(input: $input, condition: $condition) {
    id
    name
    description
  }
}
`;
```

ミューテーションとは、通常のデータベースに置ける、CREATE, UPDATE, DELETEの操作を指します。
GraphQLでは、データを変えうる処理をまとめてミューテーションと呼んでいるようですね。

続いて、`queries.js`を確認して見ましょう。

```javascript:queries.js

/* eslint-disable */
// this is an auto generated file. This will be overwritten

export const getTodo = `query GetTodo($id: ID!) {
  getTodo(id: $id) {
    id
    name
    description
  }
}
`;
export const listTodos = `query ListTodos(
  $filter: ModelTodoFilterInput
  $limit: Int
  $nextToken: String
) {
  listTodos(filter: $filter, limit: $limit, nextToken: $nextToken) {
    items {
      id
      name
      description
    }
    nextToken
  }
}
`;

```

getTodoは、特定のTodoを取得するもので、listTodosは、存在するTodoをリスト形式でまとめて取得するものです。

さらに、`subscription.js`を確認していきます。

```javascript:subscription.js
/* eslint-disable */
// this is an auto generated file. This will be overwritten

export const onCreateTodo = `subscription OnCreateTodo {
  onCreateTodo {
    id
    name
    description
  }
}
`;
export const onUpdateTodo = `subscription OnUpdateTodo {
  onUpdateTodo {
    id
    name
    description
  }
}
`;
export const onDeleteTodo = `subscription OnDeleteTodo {
  onDeleteTodo {
    id
    name
    description
  }
}
`;

```
AppSyncを使うことで、データの変更を監視する事ができます。新たなTodoが追加されたり、既存のTodoの変更・削除されたタイミングで、即座にデータを反映する事ができます。

#Vueの編集

実際にTodoを追加できるように、Vueファイルを編集していきます。
まずは、`views/Home.vue`を編集します。

```vuejs:Home.vue

<template>
  <div class="home">
      <new-todo></new-todo>
      <amplify-connect :query="listTodosQuery"
          :subscription="createTodoSubscription"
          :onSubscriptionMsg="onCreateTodo">
        <template slot-scope="{loading, data, errors}">
          <div v-if="loading">Loading...</div>

          <div v-else-if="errors.length > 0"></div>

          <div v-else-if="data">
            <TodoList :items="data.listTodos.items"></TodoList>
          </div>
        </template>
      </amplify-connect>
  </div>
</template>

<script>
import { components } from 'aws-amplify-vue';
import TodoList from '@/components/TodoList.vue';
import NewTodo from '@/components/NewTodo.vue';

const ListTodosQuery = `query ListTodos {
    listTodos {
      items {
        id
        name
      }
    }
  }`;

  const OnCreateTodoSubscription = `subscription OnCreateTodo {
      onCreateTodo {
        id
        name
      }
    }`;

export default {
  name: 'home',
  components: {
    NewTodo,
    TodoList,
    ...components
  },
  computed: {
    listTodosQuery() {
      return this.$Amplify.graphqlOperation(ListTodosQuery);
    },
    createTodoSubscription() {
      return this.$Amplify.graphqlOperation(OnCreateTodoSubscription);
    }
  },
  methods: {
    onCreateTodo(prevData, newData) {
      console.log('New todo from subscription...');
      const newTodo = newData.onCreateTodo;
      prevData.data.listTodos.items.push(newTodo);
      return prevData.data;
    }
  }
}
</script>
```

続いて、`components/NewTodo.vue`を追加します。

```vuejs:NewTodo.vue
<template>
  <div>
    <amplify-connect :mutation="createTodoMutation" @done="onCreateFinished">
      <template slot-scope="{ loading, mutate, }">
        <input v-model="name" placeholder="item name" />
        <input v-model="description" placeholder="item description" />
        <button :disabled="loading" @click="mutate">Create Todo</button>
      </template>
    </amplify-connect>
  </div>
</template>

<script>
import { components } from 'aws-amplify-vue';

const CreateTodoMutation = `mutation CreateTodo($name: String!, $description: String) {
    createTodo(input: { name: $name, description: $description }) {
      id
      name
    }
  }`;

export default {
  name: 'NewTodo',
  components: {
    ...components
  },
  data () {
    return {
      name: '',
      description: ''
    }
  },
  computed: {
    createTodoMutation () {
      return this.$Amplify.graphqlOperation(CreateTodoMutation,
        { name: this.name, description: this.description });
    }
  },
  methods: {
    onCreateFinished () {
      console.log('Todo created!');
    }
  }
}
</script>
```

さらに、`components/TodoList.vue`を編集していきます。

```vuejs:TodoList.vue
<template>
  <div>
    <ul v-for="item in items" :key="item.id">
      <li>
        {{item}}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props:['items'],
}
</script>
```

## 解説
amplify-connectコンポーネントの実装は[こちら](https://github.com/aws-amplify/amplify-js/blob/master/packages/aws-amplify-vue/src/components/api/graphql/Connect.vue)から確認する事ができます。
クエリを読み込んで、そのまま実行している事がわかります。


## 実行
<img width="1440" alt="スクリーンショット 2020-01-29 18.20.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163480/049987f4-0280-ae9d-2d56-d4e5a1fbb58c.png">
新たなTodoが追加されているのが確認できます。

# データ構造
では、GraphQLで追加したデータがどのように格納されているか確認して見ましょう。
AWSコンソールでDynamoDBにアクセスします。
<img width="1433" alt="スクリーンショット 2020-01-29 18.23.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163480/6a248998-ed02-26a8-c20b-78420aa25eff.png">
テーブルにアクセスします。
<img width="1228" alt="スクリーンショット 2020-01-29 18.26.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163480/ff1c1542-f73d-09b8-066d-3975685c99ed.png">

固有のIDと、型名、作成時刻、各パラメータ、更新時刻が格納されています。IDがパーテーションキーになっているので、IDを使うと$O(1)$でデータを取得する事ができます。

# ディレクティブ
これからはより複雑なスキーマを作っていきます。

### @keyディレクティブ
@keyディレクティブを追加すると、keyに指定した属性で検索できるようになります。
今回はメールアドレスの一意性を用いてユーザを追加します。
まずはschema.graphqlを変更します。

```graphql:schema.graphql
type Todo @model {
  id: ID!
  name: String!
  description: String
}

type User @model @key(fields: ["email"]) {
  email: String!
  username: String!
}
```

スキーマをローカルでただ変更しただけでは、実際に使用することはできません。

```
$ amplify push
```
を再度行って、ローカルのスキーマを更新します。

続いて、Userデータを更新するコンポーネントを作成します。

```vuejs:NewUser.vue
<template>
  <div>
    <amplify-connect :mutation="createUserMutation" @done="onCreateFinished">
      <template slot-scope="{ loading, mutate, }">
        <input v-model="email" placeholder="email" />
        <input v-model="username" placeholder="username" />
        <button :disabled="loading" @click="mutate">Create User</button>
      </template>
    </amplify-connect>
  </div>
</template>

<script>
import { components } from 'aws-amplify-vue';

const CreateUserMutation = `mutation CreateUser($email: String!, $username: String!) {
    createUser(input: { email: $email, username: $username }) {
      email
      username
    }
  }`;

export default {
  name: 'NewUser',
  components: {
    ...components
  },
  data () {
    return {
      email: '',
      username: ''
    }
  },
  computed: {
    createUserMutation () {
      return this.$Amplify.graphqlOperation(CreateUserMutation,
        { email: this.email, username: this.username });
    }
  },
  methods: {
    onCreateFinished () {
      console.log('User created!');
    }
  }
}
</script>
```


続いて、メールアドレスを使って、ユーザ名をクエリする関数を作ります。

```vuejs:Home.vue
async getUser(){
      const userInfo = await API.graphql(graphqlOperation(queries.getUser, { email: this.email }))
      console.log(userInfo)
    }
```


実際に実行すると、

```bash:結果
{data: {…}}data: getUser: {email: "test@example.com", username: "John Doe"}__proto__: Object__proto__: Object
```

このように正しく先ほど追加したデータを見ることができます！

## GraphQLクエリのAppSyncでの実行

今回NewUser.vueのなかで作成したmutationをもう一度見てみましょう。

```graphql:作成したmutation
mutation CreateUser($email: String!, $username: String!) {
    createUser(input: { email: $email, username: $username }) {
      email
      username
    }
  }
```

CreateUserという関数（外側）の中で、`$email`, `$username`という変数を定義して、その中で
createUser（内側）を実行しています。

続いて、AppSync上でこのmutationを実行してみましょう。
まずはAWSコンソールにログインして、AppSyncを開きます。
<img width="1030" alt="スクリーンショット 2020-03-01 16.35.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163480/9f18f5ce-e311-f2c5-5802-0ac3539a23b1.png">
「クエリを実行」をクリック
<img width="472" alt="スクリーンショット 2020-03-01 16.37.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163480/7b4475d4-b47b-e97d-f368-5fd113f044fb.png">
「ユーザープールでログイン」をクリック。
これはAPIの認証形式によりますので、設定中の形式でログインしてください。
Cognitoの場合の例を示します。

|パラメータ|入力内容|
|:---|:---|
|ClientId|aws-exports.jsのaws_user_pools_web_client_id|
|ユーザー名|ログインできる有効なユーザのメールアドレス|
|パスワード|ログインできる有効なユーザのパスワード|

続いて、実際にmutationを実行してみましょう。
先ほどは```$email```のように`$`を用いて変数を表していましたが、今回は直打ちで実行します。

```graphql:入力するクエリ
mutation CreateUser {
    createUser(input: { email: "test2@example.com" username: "Jane Doe"}) {
      email
      username
    }
  }
```
クエリを入力して実行すると、
<img width="1013" alt="スクリーンショット 2020-03-01 17.03.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163480/9d2da4bb-c449-7aff-d915-8ec209bb7d6e.png">
このように登録完了を示すレスポンスが返ります。

DynamoDBでみてみると、
<img width="997" alt="スクリーンショット 2020-03-01 17.02.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163480/3a1b9e29-6514-3d3d-046f-637f35546673.png">
実際に追加されていることがわかります。


好評であれば次回に続く