AWS AmplifyとReactをつかってToDoタスクの管理アプリを作成してみました。簡単にそれっぽいものが作れるので、開発過程を記したいと思います。
背景
AWSに興味を持ち始めて、AWSを利用したアプリを開発したいなぁと思っていたある日、わが社に入社された新卒の方々が研修をしていました。
そこで、新卒の方が多忙な研修タスクを管理できるように、個人的に気になっていたAWS AmplifyをつかってToDoアプリを作ろうと思いました。
AWS Amplifyとは
AWS Amplifyは、フロントエンドのウェブ/モバイルアプリケーションを構築するための開発プラットフォームです。様々なAWSサービスと連携して、バックエンドの複雑さを意識せずに簡単にアプリ開発・デプロイできます。
AWS Amplifyは、サービス・ツールで構成されています。今回主に利用するものについて記します。
Amplify CLI
各種AWSサービスを簡単に利用できる、コマンドベースのツールチェーンです。クラウドバックエンドの構築や管理のほか、AWSサービスとの連携に必要な設定ファイルやソースコードを自動生成してくれます。
AWSサービスやバックエンドについてあまり知識がなくても、開発が可能です。
Amplify Console
Amplify Consoleは、サーバーレスウェブアプリケーションを公開するための環境を、自動で構築してくれるサービスです。Gitリポジトリを紐付けておくことにより、簡単にデプロイ環境を構築してくれるほか、ホスティングまで自動実行してくれます。
-
Amplify UI Components
入力フォームなどの機能をもつUIを埋め込むためのUIコンポーネントです。
今回はせっかくなので、積極的にこのコンポーネントを利用して画面UIを作っていきます。
料金
以下の制限で12か月の無料枠があるので、個人の開発なら簡単に試していただけます。
- 1,000ビルド/月
- 5GBのストレージおよび15GBのホスティング
無料枠を超えると、以下のように従量課金されます。
- ビルド&デプロイ
- 0.01USD/ビルド分
- ホスティング
- ストレージ1GBあたり0.023USD/月
- ホスティングサービス1GBあたり0.15USD
開発手順
では、ここからは開発の手順を記していきます。
Reactアプリの作成
まずは以下のコマンドを実行して、Reactアプリを作成します。
$ npx create-react-app todo
以下のコマンドで作成されたディレクトリに移動して、アプリを実行します。
$ cd todo
$ npm start
GitHubのリポジトリへのアプリのプッシュ
今回はGitHubのリポジトリからAWS Amplifyにコードを接続するため、GitHubのリポジトリに作成したアプリをプッシュする必要があります。
GitHubにリポジトリを作成します。
Repository Name
などを設定して、Create repository
ボタンをクリックします。
次に以下のコマンドを実行し、作成したReactアプリのGutを初期化します。
$ ls -al | grep git
$ rm -rf .git/
以下のコマンドを実行し、作成したリポジトリにアプリをプッシュします。
$ git init
$ git remote add origin https://github.com/*ユーザー名*/*リポジトリー名*.git
$ git add .
$ git commit -m "initial commit"
$ git push origin master
AWS Amplifyでのアプリのデプロイ
AWSマネジメントコンソールにログインし、AWS Amplifyにアクセスします。
まだAWS Amplifyを使用したことがない場合は、以下のような画面に遷移しますので、ウェブアプリケーションをホスト
の使用を開始する
をクリックします。
今回はGitHubのコードを接続するので、GitHub
を選択して続行
をクリックします。
すると、GitHubの認証を求められるので、画面指示に従って操作を行います。
認証に成功すると以下の画面に遷移します。最近更新されたリポジトリ
で作成したリポジトリを選択し、ブランチ
を選択したら次へ
をクリックします。
次に遷移した画面はそのまま次へ
をクリックすると、確認画面に遷移します。
確認画面で保存してデプロイ
をクリックすると、AWS Amplifyはアプリのプロビジョニング、ビルド、デプロイを開始します。
以下の画面のステータスバーでデプロイ
が成功していれば、AWS Amplifyでのアプリのデプロイが完了です。
赤線で囲んだURLにアクセスすると、作成したアプリの画面が開きます。
今後、コードを変更した場合、そのコードをmain
ブランチにプッシュすると、自動的に新しくビルドが開始され、アプリがデプロイされます。
ローカルのAmplifyアプリの初期化
Amplifyプロジェクトで開発しながら、新しい機能を追加できるようにAmplifyプロジェクトをローカル環境に落とし込んでいきます。
以下のコマンドでAmplify CLIをインストールします。Amplify CLI を使用することで、ターミナルから AWSサービスを直接作成、管理、削除できます。
$ npm install -g @aws-amplify/cli
Amplify CLIのインストールが完了したら、Amplifyの設定を行います。以下のコマンドを実行します。
$ amplify configure
リージョンを入力すると、IAMユーザーの作成画面が表示されます。
IAMユーザーの作成画面では、ユーザー名を入力すればデフォルトで既存のポリシーが設定されているので、そのまま次へ進んでユーザーを作成してください。ユーザー作成後は、認証情報(CSV)をダウンロードして保存しておいてください。
その後ターミナルに戻り、ユーザー名を入力して、Enter
キーを押します。
Specify the AWS Region
? region: ap-northeast-1
Specify the username of the new IAM user:
? user name: xxxxxx
先ほど作成したアクセスキーIDとアクセスキーを入力してください。
Enter the access key of the newly created user:
? accessKeyId: ********************
? secretAccessKey: ****************************************
Amplifyの設定が完了したら、Amplifyアプリを初期化します。
AWSマネジメントコンソールで作成したAmplifyコンソールにアクセスします。
Backend environments
タブをクリックし、Get started
をクリックします。
バックエンドがデプロイされるので、完了するまで待ちます。
Studioを起動する
をクリックすると、バックエンドを管理するためのビジュアルインターフェイスが別タブで開きます。
Backend environments
タブに戻り、ローカル設定手順
をクリックします。
表示されるコマンドをターミナルで実行します。
手順に従ってセットアップします。
? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
? What javascript framework are you using react
? Source Directory Path: src
? Distribution Directory Path: build
? Build Command: npm run-script build
? Start Command: npm run-script start
? Do you plan on modifying this backend? Y
認証機能の追加
作成したアプリに認証機能を追加します。Amazon Cognitoを活用して、Amplify CLIとライブラリでユーザーを認証します。
Amplifyライブラリのインストール
まず、以下を実行してAmplifyライブラリの@aws-amplify/ui-react
をインストールします。
aws-amplify
ライブラリには、さまざまなAWSサービスと対話するためのクライアント側のAPIがすべて含まれており、@aws-amplify/ui-react
ライブラリにはフレームワーク固有のUIコンポーネントが含まれています。
$ npm install aws-amplify @aws-amplify/ui-react
認証サービスの作成
以下のコードを実行することで、Amplify CLIを利用して認証サービスを作成します。
$ amplify add auth
? Do you want to use the default authentication and security configuration? Default configuration
? How do you want users to be able to sign in? Username
? Do you want to configure advanced settings? No, I am done.
認証サービスの導入
次に、以下を実行して認証サービスをデプロイすれば、認証サービスを導入することができます。
$ amplify push --y
アプリでの認証機能の利用
認証サービスが導入できたので、Reactのアプリを認証機能が利用できるように修正していきます。
以下のようにコードを修正します。
import logo from "./logo.svg";
// 以下が認証機能のためのインポート文
import "@aws-amplify/ui-react/styles.css";
import {
withAuthenticator,
Button,
Heading,
Image,
View,
Card,
} from "@aws-amplify/ui-react";
function App({ signOut }) {
return (
<View className="App">
<Card>
<Image src={logo} className="App-logo" alt="logo" />
<Heading level={1}>We now have Auth!</Heading>
</Card>
// サインアウトボタンの追加
<Button onClick={signOut}>Sign Out</Button>
</View>
);
}
// withAuthenticatorコンポーネントを利用
export default withAuthenticator(App);
withAuthenticator
コンポーネントを使用することで、ユーザー認証フロー全体を足場にして、ユーザーがサインアップ、サインイン、パスワードのリセット、多要素認証 (MFA) のサインインの確認を行えるようにします。
以下を実行してアプリを起動します。
$ npm start
アプリを起動すると、以下のように認証画面が表示されるようになります。Create Account
タブで情報を入力してCreate Account
ボタンをクリックすると入力したメールアドレスに確認コードが送信されます。確認コードを入力するとサインインできます。
サインインできると、サインアウトボタンが表示されます。サインアウトボタンをクリックするとサインアウトでき、確認画面のSign In
タブから再度サインインできます。
Amplify Studioを起動して、再度メニューUser management
をクリックすると、登録されたユーザーの情報を確認したり、ユーザーグループを設定したりできます。
Authentication
メニューからは、パスワードポリシーの設定など認証機能の設定を行うことができます。
データベースの利用
AWS Amplifyでは、AWS AppSyncを使用するGraphQL APIを作成することでAmazon DynamoDBと連携することができます。
今回はAmazon DynamoDBでToDoタスクを追加、編集、削除できるようにしていきます。
APIの追加
以下を実行して、GraphQL APIを追加します。
$ amplify add api
? Select from one of the below mentioned services: GraphQL
? Here is the GraphQL API that we will create. Select a setting to edit or continue: Continue
? Choose a schema template: Single object with fields (e.g., "Todo" with ID, name, description)
? Do you want to edit the schema now? (Y/n) yes
データベースの作成
/amplify/backend/api/<api_name>/schema.graphql
をエディターで開き、以下を追記して保存します。
ちなみにデータのスカラー型はこちらを参照ください。
type Todo @model @auth(rules: [{ allow: public }]) {
id: ID! // 各ToDoタスクのID
title: String! // タイトル
status: String // ステータス
priority: String // 優先度
start: AWSDate // マイルストーンの開始日
end: AWSDate // マイルストーンの終了日
description: String // 説明
user: String! // 登録したユーザーのID
}
APIのデプロイ
以下を実行することで、以下の3つが行われます。
- AWS AppSync API を作成する
- DynamoDB テーブルを作成する
- API のクエリに使用できるローカルGraphQLオペレーションを
src/graphql
にあるフォルダーに作成する
$ amplify push --y
APIを利用したアプリ画面の作成
GraphQL APIを追加してデータベースを操作できるようになったので、次はアプリ画面を作成していきます。
今回はReact用に様々なUIコンポーネントが用意されている@aws-amplify/ui-react
を利用して、画面を作成していきます。
データの表示
APIを利用して取得したデータを表示するようなアプリ画面を作成していきます。
利用するコンポーネントは認証で利用したwithAuthenticator
も含めて、以下を利用します
Amplify UI Componentsの詳細はこちらを参照ください。
- withAuthenticator,
- Button
- Flex
- Table
- TableCell
- TableBody
- TableHead
- TableRow
- Heading
- View
では、画面表示のコードを修正していきます。
src/App.js
を以下のように修正します。
import "./App.css";
import { Amplify, API } from "aws-amplify";
import config from "./aws-exports";
import "@aws-amplify/ui-react/styles.css";
import {
withAuthenticator,
Button,
Flex,
Table,
TableCell,
TableBody,
TableHead,
TableRow,
Heading,
View,
} from "@aws-amplify/ui-react";
import { listTodos } from "./graphql/queries";
import React, { useState, useEffect } from "react";
Amplify.configure(config);
function App({ signOut }) {
const [todos, setTodos] = useState([]);
useEffect(() => {
fetchTodos();
}, []);
async function fetchTodos() {
const apiData = await API.graphql({ query: listTodos });
const todosFromAPI = apiData.data.listTodos.items;
setTodos(todosFromAPI);
}
return (
<View className="App" margin="0 3rem">
<View className="header">
<Flex
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Heading level={1}>My Todo App</Heading>
<Flex direction="row">
<Button onClick={signOut}>Sign Out</Button>
</Flex>
</Flex>
</View>
<View margin="3rem 0">
<Table caption="" highlightOnHover={false}>
<TableHead>
<TableRow>
<TableCell as="th">No.</TableCell>
<TableCell as="th">Title</TableCell>
<TableCell as="th">Status</TableCell>
<TableCell as="th">Priority</TableCell>
<TableCell as="th">Milestone</TableCell>
<TableCell as="th">Discription</TableCell>
</TableRow>
</TableHead>
<TableBody>
{todos.map((todo, index) => (
<TableRow key={index}>
<TableCell>{index}</TableCell>
<TableCell>{todo.title}</TableCell>
<TableCell>{todo.status}</TableCell>
<TableCell>{todo.priority}</TableCell>
<TableCell>
{todo.start} - {todo.end}
</TableCell>
<TableCell>
{todo.description ? todo.description : "-"}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</View>
</View>
);
}
export default withAuthenticator(App);
以下のインポート文でAPI、およびGraphQLのクエリを利用できるようにします。
import { Amplify, API } from "aws-amplify";
import { listTodos } from "./graphql/queries";
fetchTodos
関数は、以下の処理でAPIを利用して登録されているすべてのToDoタスクの情報を取得しています。取得したデータは変数todos
にセットします。
await API.graphql({ query: listTodos });
todos
のデータはTable
などのコンポーネントで表形式で表示されます。Dynamo DBにToDoタスクのデータがあれば、画面にToDoタスクのデータが表示されます。
アプリを起動してログインすると、以下のような画面が表示されます。
データの追加・削除
データが表示できるようになりましたので、画面からデータを登録できるようにしています。
今回は、画面上のAdd+
ボタンをクリックすると、ToDoタスクを追加するためのモーダルが表示されるようにします。(モーダルにするのは筆者の好みです)
モーダル表示のためsrc/index.css
を以下のように修正します。
.overlay {
overflow: scroll;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
padding-top: 15rem;
}
.content {
overflow: auto;
z-index: 2;
width: 50%;
padding: 1em;
background: #fff;
border-radius: 4px;
}
ToDoタスクの追加モーダルのコンポーネントAddTodoModal
を作成します。
src
ディレクトリ下にcomponents
ディレクトリを作成します。
src/components/AddTodoModal.js
を作成し、以下のように編集します。
import React, { useState } from "react";
import {
Button,
Flex,
Heading,
SelectField,
TextField,
TextAreaField,
View,
} from "@aws-amplify/ui-react";
import { DatePicker } from "@mui/x-date-pickers";
import dayjs from "dayjs";
const statusList = ["New", "Open", "On Hold", "Resolved", "Close"];
const priorityList = ["Trivial", "Minor", "Major", "Critical", "Blocker"];
export function AddTodoModal({ userId, createTodo, handleAddModal }) {
const [todo, setTodo] = useState({
user: userId,
title: undefined,
status: statusList[0],
priority: priorityList[0],
start: undefined,
end: undefined,
description: undefined,
});
return (
<div className="overlay">
<div className="content">
<View className="header">
<Heading level={2}>Add Todo</Heading>
</View>
<View margin="1rem">
<Flex direction="column">
<TextField
placeholder="Input todo title"
label="Title"
onChange={(i) => {
setTodo({ ...todo, title: i.target.value });
}}
/>
<TextAreaField
label="Discription"
placeholder="Input todo discription"
onChange={(i) => {
setTodo({ ...todo, description: i.target.value });
}}
/>
<SelectField
label="Status"
onChange={(i) => {
setTodo({ ...todo, status: i.target.value });
}}
>
{statusList.map((item, i) => (
<option key={i} value={item}>
{item}
</option>
))}
</SelectField>
<SelectField
label="Priority"
onChange={(i) => {
setTodo({ ...todo, priority: i.target.value });
}}
>
{priorityList.map((item, i) => (
<option key={i} value={item}>
{item}
</option>
))}
</SelectField>
<Flex
direction="row"
justifyContent="space-between"
alignItems="center"
margin="1rem 0"
>
<DatePicker
label="Start Date"
onChange={(i) => {
setTodo({ ...todo, start: dayjs(i).format("YYYY-MM-DD") });
}}
/>
<span>-</span>
<DatePicker
label="End Date"
onChange={(i) => {
setTodo({ ...todo, end: dayjs(i).format("YYYY-MM-DD") });
}}
/>
</Flex>
</Flex>
<Flex
direction="row"
justifyContent="flex-end"
alignItems="center"
margin="1rem 0"
>
<Button onClick={handleAddModal}>Cansel</Button>
<Button
onClick={() => {
createTodo(todo);
handleAddModal();
}}
>
Add
</Button>
</Flex>
</View>
</div>
</div>
);
}
AddTodoModal
コンポーネントには以下を引数として渡す必要があります。
- userId: ログインしているユーザーの情報
- createTodo: APIを利用してデータを追加する関数
- handleAddModal: 追加モーダルの表示を切り替える関数
AddTodoModal
コンポーネントの入力フォームでは、@aws-amplify/ui-react
の以下のコンポーネントを利用しています。
- SelectField
- TextField
- TextAreaField
マイルストーンを設定するための日付選択コンポーネントは、まだ@aws-amplify/ui-react
になさそうだったので、@mui/x-date-pickers
をインストールしてDatePicker
コンポーネントを使いました。
DatePicker
コンポーネントを利用するために、以下をインストールしてコンポーネントを設定する必要がありますので、とりあえず以下を実行して必要なライブラリをインストールしましょう。
$ npm install @mui/x-date-pickers
$ npm install dayjs
$ npm install @mui/material @emotion/react @emotion/styled
$ npm install @mui/styled-engine-sc styled-components
一旦、AddTodoModal
コンポーネントが作成できました。
次にAPIを利用してデータの追加、削除できるようにsrc/App.js
を以下のように修正します。
import "./App.css";
import { Amplify, Auth, API } from "aws-amplify";
import config from "./aws-exports";
import "@aws-amplify/ui-react/styles.css";
import {
withAuthenticator,
Button,
Flex,
Table,
TableCell,
TableBody,
TableHead,
TableRow,
Heading,
View,
} from "@aws-amplify/ui-react";
import { listTodos } from "./graphql/queries";
import {
createTodo as createTodoMutation,
deleteTodo as deleteTodoMutation,
} from "./graphql/mutations";
import React, { useState, useEffect } from "react";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { AddTodoModal } from "./components/AddTodoModal";
Amplify.configure(config);
function App({ signOut }) {
const [user, setUser] = useState({ id: "", name: "" });
const [todos, setTodos] = useState([]);
const [isOpenAddModal, setIsOpenAddModal] = useState(false);
useEffect(() => {
Auth.currentAuthenticatedUser().then((userInfo) => {
setUser({ id: userInfo.attributes.sub, name: userInfo.username });
});
}, []);
useEffect(() => {
fetchTodos();
}, []);
async function fetchTodos() {
const apiData = await API.graphql({ query: listTodos });
const todosFromAPI = apiData.data.listTodos.items;
setTodos(todosFromAPI);
}
function handleAddModal() {
if (isOpenAddModal === false) {
setIsOpenAddModal(true);
} else if (isOpenAddModal === true) {
setIsOpenAddModal(false);
}
}
async function createTodo(event) {
const data = {
title: event.title,
status: event.status,
priority: event.priority,
start: event.start,
end: event.end,
description: event.description,
user: event.user,
};
await API.graphql({
query: createTodoMutation,
variables: { input: data },
});
fetchTodos();
}
async function deleteTodo({ id }) {
const newTodos = todos.filter((todo) => todo.id !== id);
setTodos(newTodos);
await API.graphql({
query: deleteTodoMutation,
variables: { input: { id } },
});
}
return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<View className="App" margin="0 3rem">
<View className="header">
<Flex
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Heading level={1}>My Todo App</Heading>
<Flex direction="row">
<p>{user.name}</p>
<Button onClick={signOut}>Sign Out</Button>
</Flex>
</Flex>
</View>
<View className="add-todo-button" margin="1rem 1rem">
<Flex
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Button onClick={handleAddModal}>Add +</Button>
</Flex>
</View>
{isOpenAddModal === true && (
<AddTodoModal
userId={user.id}
createTodo={createTodo}
handleAddModal={handleAddModal}
></AddTodoModal>
)}
<View margin="3rem 0">
<Table caption="" highlightOnHover={false}>
<TableHead>
<TableRow>
<TableCell as="th">No.</TableCell>
<TableCell as="th">Title</TableCell>
<TableCell as="th">Status</TableCell>
<TableCell as="th">Priority</TableCell>
<TableCell as="th">Milestone</TableCell>
<TableCell as="th">Discription</TableCell>
<TableCell as="th"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{todos.map((todo, index) => (
<TableRow key={index}>
<TableCell>{index}</TableCell>
<TableCell>{todo.title}</TableCell>
<TableCell>{todo.status}</TableCell>
<TableCell>{todo.priority}</TableCell>
<TableCell>
{todo.start} - {todo.end}
</TableCell>
<TableCell>
{todo.description ? todo.description : "-"}
</TableCell>
<TableCell>
<Button onClick={() => deleteTodo(todo)}>Delete</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</View>
</View>
</LocalizationProvider>
);
}
export default withAuthenticator(App);
以下のimport文でGraphQLのクエリを使えるようにします。
import {
createTodo as createTodoMutation,
deleteTodo as deleteTodoMutation,
} from "./graphql/mutations";
createTodo
関数は、以下の処理でAPIを利用してToDoタスクの情報をデータベースに追加します。query
にデータ追加のクエリ、valiables
に追加するデータを指定します。
await API.graphql({
query: createTodoMutation,
variables: { input: data },
});
deleteTodo
関数は、以下の処理でAPIを利用して特定のToDoタスクの情報をデータベースから削除します。query
にデータ削除のクエリ、valiables
に削除するデータのid
を指定します。
await API.graphql({
query: deleteTodoMutation,
variables: { input: { id } },
});
以下の処理では、ログインしたユーザーの情報を取得しています。
取得したユーザーのデータはuser
オブジェクトに格納しています。user
のデータは、AddTodoModal
コンポーネントなどに渡します。
useEffect(() => {
Auth.currentAuthenticatedUser().then((userInfo) => {
setUser({ id: userInfo.attributes.sub, name: userInfo.username });
});
}, []);
また、DatePicker
コンポーネントを利用するために、以下のようにimportして画面表示する部分全体をLocalizationProvider
コンポーネントで囲みます。
import { LocalizationProvider } from "@mui/x-date-pickers";
アプリを起動してAdd+
ボタンをクリックすると、以下のようなモーダルが表示され、データをデータベースに追加できます。データを追加すると、Dynamo DBにデータが追加されていることを確認できます。
データを追加すると、画面にToDoタスクの一覧が表示されます。各タスクの右に表示されるDelete
ボタンをクリックすると、データをデータベースから削除できます。
データの編集
データの追加と削除ができるようになったので、次はデータの編集ができるようにします。
ToDoタスクの一覧で各タスクの右のEdit
ボタンをクリックすると、追加モーダルと同様に編集モーダルが表示され、データを編集できるようにします。
まず、編集モーダル用のEditTodoModal
コンポーネントを作成します。
src/components/EditTodoModal.js
を作成し、以下のように編集します。
import React, { useState } from "react";
import {
Button,
Flex,
Heading,
SelectField,
TextField,
TextAreaField,
View,
} from "@aws-amplify/ui-react";
import { DatePicker } from "@mui/x-date-pickers";
import dayjs from "dayjs";
const statusList = ["New", "Open", "On Hold", "Resolved", "Close"];
const priorityList = ["Trivial", "Minor", "Major", "Critical", "Blocker"];
export function EditTodoModal({
userId,
preTodo,
updateTodo,
handleEditModal,
}) {
const [todo, setTodo] = useState({ ...preTodo, user: userId });
return (
<div className="overlay">
<div className="content">
<View className="header">
<Heading level={2}>Edit Todo</Heading>
</View>
<View margin="1rem">
<Flex direction="column">
<TextField
placeholder="Input todo title"
label="Title"
value={todo.title}
onChange={(i) => {
setTodo({ ...todo, title: i.target.value });
}}
/>
<TextAreaField
label="Discription"
placeholder="Input todo discription"
value={todo.description}
onChange={(i) => {
setTodo({ ...todo, description: i.target.value });
}}
/>
<SelectField
label="Status"
value={todo.status}
onChange={(i) => {
setTodo({ ...todo, status: i.target.value });
}}
>
{statusList.map((item, i) => (
<option key={i} value={item}>
{item}
</option>
))}
</SelectField>
<SelectField
label="Priority"
value={todo.priority}
onChange={(i) => {
setTodo({ ...todo, priority: i.target.value });
}}
>
{priorityList.map((item, i) => (
<option key={i} value={item}>
{item}
</option>
))}
</SelectField>
<Flex
direction="row"
justifyContent="space-between"
alignItems="center"
margin="1rem 0"
>
<DatePicker
label="Start Date"
value={dayjs(todo.start)}
onChange={(i) => {
setTodo({ ...todo, start: dayjs(i).format("YYYY-MM-DD") });
}}
/>
<span>-</span>
<DatePicker
label="End Date"
value={dayjs(todo.end)}
onChange={(i) => {
setTodo({ ...todo, end: dayjs(i).format("YYYY-MM-DD") });
}}
/>
</Flex>
</Flex>
<Flex
direction="row"
justifyContent="flex-end"
alignItems="center"
margin="1rem 0"
>
<Button onClick={handleEditModal}>Cansel</Button>
<Button
onClick={() => {
updateTodo(todo);
handleEditModal();
}}
>
Edit
</Button>
</Flex>
</View>
</div>
</div>
);
}
EditTodoModal
コンポーネントには以下を引数として渡す必要があります。
- userId: ログインしているユーザーの情報
- preTodo: すでに追加されているToDoタスクのデータ
- updateTodo: APIを利用してデータを更新する関数
- handleEditModal: 編集モーダルの表示を切り替える関数
EditTodoModal
コンポーネントが作成できました。
次にAPIを利用してデータの編集できるようにsrc/App.js
を以下のように修正します。
import "./App.css";
import { Amplify, Auth, API } from "aws-amplify";
import config from "./aws-exports";
import "@aws-amplify/ui-react/styles.css";
import {
withAuthenticator,
Button,
Flex,
Table,
TableCell,
TableBody,
TableHead,
TableRow,
Heading,
View,
} from "@aws-amplify/ui-react";
import { listTodos } from "./graphql/queries";
import {
createTodo as createTodoMutation,
updateTodo as updateTodoMutation,
deleteTodo as deleteTodoMutation,
} from "./graphql/mutations";
import React, { useState, useEffect } from "react";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { AddTodoModal } from "./components/AddTodoModal";
import { EditTodoModal } from "./components/EditTodoModal";
Amplify.configure(config);
function App({ signOut }) {
const [user, setUser] = useState({ id: "", name: "" });
const [todos, setTodos] = useState([]);
const [preTodo, setPreTodo] = useState({});
const [isOpenAddModal, setIsOpenAddModal] = useState(false);
const [isOpenEditModal, setIsOpenEditModal] = useState(false);
useEffect(() => {
Auth.currentAuthenticatedUser().then((userInfo) => {
setUser({ id: userInfo.attributes.sub, name: userInfo.username });
});
}, []);
useEffect(() => {
fetchTodos();
}, []);
async function fetchTodos() {
const apiData = await API.graphql({ query: listTodos });
const todosFromAPI = apiData.data.listTodos.items;
setTodos(todosFromAPI);
}
function handleAddModal() {
if (isOpenAddModal === false) {
setIsOpenAddModal(true);
} else if (isOpenAddModal === true) {
setIsOpenAddModal(false);
}
}
function handleEditModal() {
if (isOpenEditModal === false) {
setIsOpenEditModal(true);
} else if (isOpenEditModal === true) {
setIsOpenEditModal(false);
}
}
async function createTodo(event) {
const data = {
title: event.title,
status: event.status,
priority: event.priority,
start: event.start,
end: event.end,
description: event.description,
user: event.user,
};
await API.graphql({
query: createTodoMutation,
variables: { input: data },
});
fetchTodos();
}
async function updateTodo(event) {
const data = {
id: event.id,
title: event.title,
status: event.status,
priority: event.priority,
start: event.start,
end: event.end,
description: event.description,
user: event.user,
};
await API.graphql({
query: updateTodoMutation,
variables: { input: data },
});
fetchTodos();
}
async function deleteTodo({ id }) {
const newTodos = todos.filter((todo) => todo.id !== id);
setTodos(newTodos);
await API.graphql({
query: deleteTodoMutation,
variables: { input: { id } },
});
}
return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<View className="App" margin="0 3rem">
<View className="header">
<Flex
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Heading level={1}>My Todo App</Heading>
<Flex direction="row">
<p>{user.name}</p>
<Button onClick={signOut}>Sign Out</Button>
</Flex>
</Flex>
</View>
<View className="add-todo-button" margin="1rem 1rem">
<Flex
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Button onClick={handleAddModal}>Add +</Button>
</Flex>
</View>
{isOpenAddModal === true && (
<AddTodoModal
userId={user.id}
createTodo={createTodo}
handleAddModal={handleAddModal}
></AddTodoModal>
)}
{isOpenEditModal === true && (
<EditTodoModal
userId={user.id}
preTodo={preTodo}
updateTodo={updateTodo}
handleEditModal={handleEditModal}
></EditTodoModal>
)}
<View margin="3rem 0">
<Table caption="" highlightOnHover={false}>
<TableHead>
<TableRow>
<TableCell as="th">No.</TableCell>
<TableCell as="th">Title</TableCell>
<TableCell as="th">Status</TableCell>
<TableCell as="th">Priority</TableCell>
<TableCell as="th">Milestone</TableCell>
<TableCell as="th">Discription</TableCell>
<TableCell as="th"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{todos.map((todo, index) => (
<TableRow key={index}>
<TableCell>{index}</TableCell>
<TableCell>{todo.title}</TableCell>
<TableCell>{todo.status}</TableCell>
<TableCell>{todo.priority}</TableCell>
<TableCell>
{todo.start} - {todo.end}
</TableCell>
<TableCell>
{todo.description ? todo.description : "-"}
</TableCell>
<TableCell>
<Button
margin="0 1rem"
onClick={() => {
setPreTodo(todo);
handleEditModal();
}}
>
Edit
</Button>
<Button onClick={() => deleteTodo(todo)}>Delete</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</View>
</View>
</LocalizationProvider>
);
}
export default withAuthenticator(App);
以下のようにimport文を追記してGraphQL更新のクエリを使えるようにします。
import {
createTodo as createTodoMutation,
updateTodo as updateTodoMutation,
deleteTodo as deleteTodoMutation,
} from "./graphql/mutations";
updateTodo
関数は、以下の処理でAPIを利用してデータベースのToDoタスクの情報を更新します。query
にデータ編集のクエリ、valiables
に編集するデータを指定します。
await API.graphql({
query: createTodoMutation,
variables: { input: data },
});
これでアプリ画面の作成も完了です。
アプリを起動して、タスク一覧のEdit
ボタンをクリックすると、編集モーダルで各toDoタスクのデータを更新できます。
デプロイ
アプリの作成が完了したので、最後にmaster
ブランチにプッシュしてアプリをデプロイしましょう。
これで、認証機能を備えたToDoタスクの管理アプリを公開することができました。
まとめ
AWS Amplifyを利用して簡単なToDoアプリを開発して、公開までしてみました。
データベースの利用やデプロイなどバックエンドのことはほとんど意識せずに、アプリ開発ができました。実際にこの記事の中でもでも実装についての内容が多く、バックエンドの内容は少なくなってしまいました。
Amplifyの中にUIコンポーネントも用意されているので、フロントの実装もやりやすかったです。
VueやAngularなどにも対応しているので、すでにReact以外で開発したアプリでも簡単に様々なAWSサービスと連携させることができそうです。他にもいろいろと設定をいじったり、ToDoアプリもいろいろ機能を搭載できそうなので、今後も開発を進めていきたいと思います。