[AWS Amplify] Todoアプリを Vue.js 3のComposition APIを使って書いてみる

AWS Amplify, IT記事

Todo アプリを作ってみたいと思います。

github : https://github.com/opvelll/amplifyTodoVue

バックエンドはAWS Amplify を使います。

フロント部分では、Vue.js v3 の新機能、Composition Api を使い記述します。

Vue v3プロジェクト作成

まず、vue cli でVueプロジェクトを作ります。

久しぶりなので vue cli をアップデートします。

vue create でシンプルなプロジェクトを作り、移動して、Vue 3の機能を使うために vue-nextを使います。

npm update -g @vue/cli
vue create vuetodo3

? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Linter
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

cd .\vueTodo3
vue add vue-next

Amplify の設定する

こちらも間が空いたので、amplify cliをアップデートします。

そして amplifyのプロジェクト設定をします。

npm update -g @aws-amplify/cli
amplify init

amplify init の質問に以下のように答えます

? Enter a name for the project vuetodo3 
? Enter a name for the environment dev
? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using vue
? Source Directory Path:  src
? Distribution Directory Path: dist
? Build Command:  npm.cmd run-script build
? Start Command: npm.cmd run-script serve
Using default provider  awscloudformation

? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use default

ほとんどそのままEnterKey押します、最後 aws cliで使う AWS profile を default から変えたので指定しました。

Amplify API を追加

プロジェクトに Amplify API を追加します。

amplify add api

最初の質問でGraphQL を選びます。

GraphQLスキーマを生成しています。(Todo)

? Please select from one of the below mentioned services: GraphQL
? Provide API name: vuetodo3
? Choose the default authorization type for the API API key
? Enter a description for the API key:
? After how many days from now the API key should expire (1-365): 365
? Do you want to configure advanced settings for the GraphQL API No, I am done.
? Do you have an annotated GraphQL schema? No
? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description)
? Do you want to edit the schema now? No

質問では、認証方法にAPI keyを選びます。期間を365日にしています。

スキーマを編集してTask型を作る

質問に答えたあと生成されたTodoスキーマを、エディタで編集します。

{project root}\amplify\backend\api\vuetodo3\schema.graphqlを開き以下のように編集します。

type Task @model {
  id: ID!
  name: String!
  completed: Boolean!
  createdAt: AWSDateTime
}

名前をTaskにして、タスクをこなしたかの項目(completed)と、生成された時間(createdAt)を足します。

BooleanやAWSDAteTimeなど型についての情報は、AWS AppSyncにドキュメントがあります。

スキーマを編集したら、amplify pushというコマンドで、AWSに環境を作ってもらいます。

amplify push

? Do you want to generate code for your newly created GraphQL API Yes
? Choose the code generation language target javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions src\graphql\**\*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 3

質問では、スキーマファイルを生成するかや、ネストの深さを指定します。だいたいyesでOK。

すると、DynamoDBとAppSyncの環境が作られています。

Vue で Todo画面を作る

バックエンド側は用意できたので、フロント側を制作します。

npmで必要ライブラリをインストール

npm で必要ライブラリをインストールします。

npm install aws-amplify @aws-amplify/ui-vue --save

プロジェクトを編集する

vue create したときに作られたプロジェクトを編集していきます。

~ 横道 : @aws-amplify/ui-vue をVue v3で使えるようにする。

aws-amplify/ui-vue が Vue v3 に対応してなかったので、無理やり使えるようにします。

\node_modules\@aws-amplify\ui-vue\dist\index.js のエラー箇所をコメントアウト

import { applyPolyfills, defineCustomElements, } from '@aws-amplify/ui-components/loader';
import Vue from 'vue';
// Tell Vue to ignore all components defined in the @aws-amplify/ui-components
// package. The regex assumes all components names are prefixed
// 'amplify-'
// Vue.config.ignoredElements = [/amplify-\w*/]; <- ここ
// Bind the custom elements to the window object
applyPolyfills().then(() => {
    defineCustomElements(window);
});
//# sourceMappingURL=index.js.map

\src\main.js のインポート部分を以下のようにします。

import { createApp } from "vue";
import App from "./App.vue";

import Amplify from "aws-amplify";
import "@aws-amplify/ui-vue";
import aws_exports from "./aws-exports";

Amplify.configure(aws_exports);

const app = createApp(App);
app.config.ignoredElements = [/amplify-\w*/];
app.mount("#app");

横道ここまで ~

App.vue ではTodoコンポーネントを呼び出します。

<template>
  <div id="app">
    <todo></todo>
  </div>
</template>

<script>
import Todo from "./components/Todo.vue";

export default {
  name: "App",
  components: {
    Todo,
  },
};
</script>

Todo コンポーネントを書く

Composition API を使って書いたら以下のようになりました。

<template>
  <div class="container">
    <div>
      <!-- todo list -->
      <div>
        <h3>Todo List</h3>
        <div class="todo-container" v-for="item in taskList" :key="item.id">
          <input
            type="checkbox"
            name="todo"
            v-model="item.completed"
            @click="updateTodo(item, $event)"
          />
          <span class="todo-name" :class="{ strikethrough: item.completed }">{{
            item.name
          }}</span>
          <button @click="deleteTodo(item.id)">消去</button>
        </div>
      </div>
      <!-- create todo form -->
      <form class="create_form" @submit.prevent="createTodo">
        <div>新しいタスク</div>
        <textarea
          v-model="createForm.name"
          name="todoName"
          rows="4"
          cols="40"
          placeholder="タスク名"
        ></textarea>
        <div class="createForm-footer">
          <button type="submit">追加</button>
        </div>
      </form>
    </div>
  </div>
</template>

<script>
import { API } from "aws-amplify";
import { listTasks } from "../graphql/queries";
import { createTask, updateTask, deleteTask } from "../graphql/mutations";
import { reactive, ref, onMounted } from "vue";

export default {
  setup() {                        // 1
    const createForm = reactive({
      name: "",
    });
    const taskList = ref([]);      // 2

    // 取得
    async function getTodoList() {
      const tasks = await API.graphql({   
        query: listTasks,
      });

      const list = tasks.data.listTasks.items;

      // ソートして画面に反映
      taskList.value = list.sort(   // 2
        (a, b) =>
          new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
      );
    }

    // 新しく作成
    async function createTodo() {
      const newTask = { name: createForm.name, completed: false };

      await API.graphql({ query: createTask, variables: { input: newTask } });  // 3

      createForm.name = "";

      await getTodoList();
    }

    // Taskの更新 チェックする
    async function updateTodo(item, event) {
      const isCompleted = event.target.checked;
      await API.graphql({
        query: updateTask,
        variables: { input: { id: item.id, completed: isCompleted } },
      });
      await getTodoList();
    }

    // Taskの削除
    async function deleteTodo(id) {
      await API.graphql({
        query: deleteTask,
        variables: { input: { id: id } },
      });
      await getTodoList();
    }

    onMounted(() => {
      getTodoList();
    });

    return {
      createForm,
      taskList,
      getTodoList,
      updateTodo,
      createTodo,
      deleteTodo,
    };
  },
};
</script>

<style scoped>
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.todo-container {
  display: flex;
  align-items: center;
}
.todo-name {
  flex-grow: 1;
}
.strikethrough {
  text-decoration: line-through;
}
.create_form {
  margin-top: 2em;
}
.createForm-footer {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}
</style>

1 setup と return で定義したプロパティと関数を、外に出しています。

2. ref や reactive でプロパティを定義しています。これで値がリアクティブになり、変更によって画面に反映されるようになります。気をつけたいのは ref で定義した値は、.value と付けてvalue部分を変更する必要があります。

3. async になっているCURD関数は、 Amplify API を使っています。別ファイルに生成された文字列の GraphQLクエリ を読み込み、フォームの値などを 引数に設定してあげると クエリや、アップデートやデリートなどのミューテーション、さらにサブスクリプションができます。

プロジェクト全体は以下です。

github : https://github.com/opvelll/amplifyTodoVue