はじめに

こんにちは、福原です。

AWS Amplifyを使用してWEBアプリを作りたいと思い、以下のチュートリアルを実施してみました。

チュートリアルを進めていく中で詰まったところを記載しているので、参考にしていただけると嬉しいです。

Reactアプリを作成

初めにReactアプリを作成します。

以下のコマンドで実行します。

以前の記事で環境構築について記載しているので、Node.jsのインストールなど参考にしてください。

npx create-react-app todo
Creating a new React app in /Users/xxxxxx/aws/react/todoapp.
~省略~
Happy hacking!

ディレクトリを移動し、アプリを立ち上げる

cd todo
npm start
Compiled successfully!
You can now view todo in the browser.
Local:            http://localhost:3000
On Your Network:  http://192.168.20.208:3000
Note that the development build is not optimized.
To create a production build, use npm run build.
webpack compiled successfully

ブラウザが立ち上がり、以下画面が表示されるとOKです。

GitHubリポジトリ作成

GitHubでリポジトリを作成します。

作成したReactアプリのGitを初期化します

初期化の前にReactアプリの.gitディレクトリを削除
※削除しないと、初期化できないため

ls -al | grep git
drwxr-xr-x   12 xxxxxx  staff      384  8 24 13:31 .git
- rw-r--r-- 1 xxxxxx staff 310 8 24 13:31 .gitignore
rm -rf .git/

作成したリポジトリにアプリケーションをプッシュします

git init
Initialized empty Git repository in /Users/xxxxx/aws/react/todo/.git/
git remote add origin https://github.com/username/todo.git
git remote -v
origin  https://github.com/username/todo.git (fetch)
origin  https://github.com/username/todo.git (push)
git add .
git commit -m "initial commit"
git push origin master

Amplifyでデプロイ

AWSにログインしAmplifyコンソールからReactアプリをデプロイします。
「ウェブアプリケーションをホスト」を選択

「GitHub」を選択し「続行」をクリック

GitHubの認証が成功すると、以下の画面が表示されます。
作成したリポジトリ/ブランチを選択し、「次へ」をクリック

※作成したリポジトリ/ブランチがない場合は、「View GitHub Permissions」を押して権限を追加します
リポジトリを選択し、「Save」をクリック

そのまま「次へ」をクリック

内容を確認し、「保存してデプロイ」をクリック

数分待つと、デプロイが完了しました

ローカルAmplifyアプリを初期化する

次にローカルでAmplifyアプリを初期化します。

まずAmplify CLIをインストールします
Amplify CLIとは各種AWSサービスを簡単に利用できる、コマンドベースのツールです。

npm install -g @aws-amplify/cli

Amplifyを設定

amplify configure
Follow these steps to set up access to your AWS account:
Sign in to your AWS administrator account:
https://console.aws.amazon.com/
Press Enter to continue

画面が表示されますが、コンソール通りにEnterをクリック
リージョン、名前を入力するとIAMユーザー作成画面が表示されます

Specify the AWS Region
? region:  ap-northeast-1
Specify the username of the new IAM user:
? user name: xxxxxx
Complete the user creation using the AWS console
https://console.aws.amazon.com/iam/home?region=ap-northeast-1#/users$new?step=final&accessKey&userNames=amplify-user&permissionType=policies&policies=arn:aws:iam::aws:policy%2FAdministratorAccess-Amplify
Press Enter to continue

ユーザー名を入力し、「次のステップ」をクリック

既存のポリシーが選択されているので、そのまま「次のステップ」をクリック

こちらもなにもせずに、「次のステップ」をクリック

内容を確認し、「ユーザーの作成」をクリック

認証情報(CSV)をダウンロードします

ターミナル(プロンプト)に戻り、Enter

先ほどの作成したアクセスID,アクセスキーを入力します

Enter the access key of the newly created user:
? accessKeyId:  ********************
? secretAccessKey:  ****************************************
This would update/create the AWS Profile in your local machine
? Profile Name:  default
Successfully set up the new user.

Amplify CLIの設定は完了したので、Amplify プロジェクトを初期化します

「-appId」は「アプリの設定」→「全般」→「アプリケーション ARN」の/までの末尾です

amplify init --appId xxxxx
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project todo
The following configuration will be applied:
Project information
| Name: xxxxx
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: react
| Source Directory Path: src
| Distribution Directory Path: build
| Build Command: npm run-script build
| Start Command: npm run-script start
? Initialize the project with the above configuration? Yes
Using default provider  awscloudformation
? Select the authentication method you want to use: AWS profile
For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
? Please choose the profile you want to use xxxxx
Amplify AppID found: xxxx. Amplify App name is: xxxxx
Adding backend environment dev to AWS Amplify app: xxxx
⠇ Initializing project in the cloud...
~省略~
Pro tip:
Try "amplify add api" to create a backend API and then "amplify push" to deploy everything

認証を追加する

Amplify ライブラリをインストール

以下のAmplify ライブラリをインストールします

AWSサービスとやり取りするためのaws-amplify ライブラリ
固有のUIコンポーネントの@ aws-amplify/ui-react ライブラリ

npm install aws-amplify @aws-amplify/ui-react

認証サービスを作成

認証サービスをAmplifyプロジェクトに追加します。

amplify add auth
Using service: Cognito, provided by: awscloudformation
The current configured provider is Amazon Cognito.
Do you want to use the default authentication and security configuration? Defau
lt configuration
Warning: you will not be able to edit these selections.
How do you want users to be able to sign in? Username
Do you want to configure advanced settings? No, I am done.
✅ Successfully added auth resource todoca616e84 locally
✅ Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

認証サービスをデプロイ

「–y」オプションで全ての質問に「yes」を設定します。

amplify push --y
~省略~
✔ All resources are updated in the cloud

Reactアプリを修正

src/index.jsを開き、最後のインポートの下に次のコードを追加します

import Amplify from 'aws-amplify';
import config from './aws-exports';
Amplify.configure(config);- src/App.jsに認証フローを追加

App.jsを次のコードに更新します。
※チュートリアルのコードではライブラリのバージョンが使用できなかったため、以下のインポート文を追加しています

import '@aws-amplify/ui-react/styles.css';
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { withAuthenticator } from '@aws-amplify/ui-react'
import '@aws-amplify/ui-react/styles.css';
function App({ signOut, user }) {
return (
<div className="App">
<header>
<img src={logo} className="App-logo" alt="logo" />
<h1>Hello {user.username}</h1>
<button onClick={signOut}>Sign out</button>
</header>
</div>
);
}
export default withAuthenticator(App);

ローカルで確認

以下の認証画面が表示されます。

GraphQLを作成

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 conti
nue Continue
? Choose a schema template: Single object with fields (e.g., “Todo” with ID, nam
e, description)
⚠️  WARNING: your GraphQL API currently allows public create, read, update, and delete access to all models via an API Key. To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql/authorization-rules
✅ GraphQL schema compiled successfully.
Edit your schema at /Users/fukuharatakuya/aws/react/todoapp/amplify/backend/api/todo/schema.graphql or place .graphql files in a directory at /Users/fukuharatakuya/aws/react/todoapp/amplify/backend/api/todo/schema
✔ Do you want to edit the schema now? (Y/n) · yes
Edit the file in your editor: /Users/fukuharatakuya/aws/react/todoapp/amplify/backend/api/todo/schema.graphql
✅ Successfully added resource todo locally
✅ Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

graphqlファイルを修正

Shema.graphqlファイルを以下に更新します。

type Note @model {
id: ID!
name: String!
description: String
}

APIをデプロイ

amplify push --y

APIを使用するためのフロントコードを記述

Src/App.jsを次のコードで更新します。

import React, { useState, useEffect } from 'react';
import logo from './logo.svg';
import './App.css';
import { withAuthenticator } from '@aws-amplify/ui-react'
import '@aws-amplify/ui-react/styles.css';
import { API } from 'aws-amplify';
import { listNotes } from './graphql/queries';
import { createNote as createNoteMutation, deleteNote as deleteNoteMutation } from './graphql/mutations';
const initialFormState = { name: '', description: '' }
function App({ signOut, user }) {
const [notes, setNotes] = useState([]);
const [formData, setFormData] = useState(initialFormState);
useEffect(() => {
fetchNotes();
}, []);
async function fetchNotes() {
const apiData = await API.graphql({ query: listNotes });
setNotes(apiData.data.listNotes.items);
}
async function createNote() {
if (!formData.name || !formData.description) return;
await API.graphql({ query: createNoteMutation, variables: { input: formData } });
setNotes([ ...notes, formData ]);
setFormData(initialFormState);
}
async function deleteNote({ id }) {
const newNotesArray = notes.filter(note => note.id !== id);
setNotes(newNotesArray);
await API.graphql({ query: deleteNoteMutation, variables: { input: { id } }});
}
return (
<div className="App">
<h1>My Notes App</h1>
<input
onChange={e => setFormData({ ...formData, 'name': e.target.value})}
placeholder="Note name"
value={formData.name}
/>
<input
onChange={e => setFormData({ ...formData, 'description': e.target.value})}
placeholder="Note description"
value={formData.description}
/>
<button onClick={createNote}>Create Note</button>
<div style={{marginBottom: 30}}>
{
notes.map(note => (
<div key={note.id || note.name}>
<h2>{note.name}</h2>
<p>{note.description}</p>
<button onClick={() => deleteNote(note)}>Delete note</button>
</div>
))
}
</div>
<img src={logo} className="App-logo" alt="logo" />
<h1>Hello {user.username}</h1>
<button onClick={signOut}>Sign out</button>
</div>
);
}
export default withAuthenticator(App);

アプリを実行してみると、メモアプリが追加されました。

ストレージを追加する

画像用のストレージを追加します。

amplify add storage
? Select from one of the below mentioned services: Content (Images, audio, video, etc.)
✔ Provide a friendly name for your resource that will be used to label this category in the project: · imageStorage
✔ Provide bucket name: · test-amplify-image-storage-xxx
✔ Who should have access: · Auth users only
✔ What kind of access do you want for Authenticated users? · create/update, read, delete
✔ Do you want to add a Lambda Trigger for your S3 Bucket? (y/N) · no
✅ Successfully added resource imageStorage locally
⚠️ If a user is part of a user pool group, run "amplify update storage" to enable IAM group policies for CRUD operations
✅ Some next steps:
"amplify push" builds all of your local backend resources and provisions them in the cloud
"amplify publish" builds all of your local backend and front-end resources (if you added hosting category) and provisions them in the cloud

graphqlファイルを修正

画像をアップロードするために、Shema.graphqlファイルを以下に更新します。

type Note @model {
id: ID!
name: String!
description: String
image: String
}

API とストレージをデプロイ

amplify push --y

Reactアプリを更新

Src/App.jsを次のコードで更新します。

import React, { useState, useEffect } from 'react';
import logo from './logo.svg';
import './App.css';
import { withAuthenticator } from '@aws-amplify/ui-react'
import '@aws-amplify/ui-react/styles.css';
import { API, Storage } from 'aws-amplify';
import { listNotes } from './graphql/queries';
import { createNote as createNoteMutation, deleteNote as deleteNoteMutation } from './graphql/mutations';
const initialFormState = { name: '', description: '' }
function App({ signOut, user }) {
const [notes, setNotes] = useState([]);
const [formData, setFormData] = useState(initialFormState);
useEffect(() => {
fetchNotes();
}, []);
async function onChange(e) {
if (!e.target.files[0]) return
const file = e.target.files[0];
setFormData({ ...formData, image: file.name });
await Storage.put(file.name, file);
fetchNotes();
}
async function fetchNotes() {
const apiData = await API.graphql({ query: listNotes });
const notesFromAPI = apiData.data.listNotes.items;
await Promise.all(notesFromAPI.map(async note => {
if (note.image) {
const image = await Storage.get(note.image);
note.image = image;
}
return note;
}))
setNotes(apiData.data.listNotes.items);
}
async function createNote() {
if (!formData.name || !formData.description) return;
await API.graphql({ query: createNoteMutation, variables: { input: formData } });
if (formData.image) {
const image = await Storage.get(formData.image);
formData.image = image;
}
setNotes([ ...notes, formData ]);
setFormData(initialFormState);
}
async function deleteNote({ id }) {
const newNotesArray = notes.filter(note => note.id !== id);
setNotes(newNotesArray);
await API.graphql({ query: deleteNoteMutation, variables: { input: { id } }});
}
return (
<div className="App">
<h1>My Notes App</h1>
<input
onChange={e => setFormData({ ...formData, 'name': e.target.value})}
placeholder="Note name"
value={formData.name}
/>
<input
onChange={e => setFormData({ ...formData, 'description': e.target.value})}
placeholder="Note description"
value={formData.description}
/>
<input
type="file"
onChange={onChange}
/>
<button onClick={createNote}>Create Note</button>
<div style={{marginBottom: 30}}>
{
notes.map(note => (
<div key={note.id || note.name}>
<h2>{note.name}</h2>
<p>{note.description}</p>
<button onClick={() => deleteNote(note)}>Delete note</button>
{
note.image && <img src={note.image} style={{width: 400}} />
}
</div>
))
}
</div>
<img src={logo} className="App-logo" alt="logo" />
<h1>Hello {user.username}</h1>
<button onClick={signOut}>Sign out</button>
</div>
);
}
export default withAuthenticator(App);

動作確認

テストのメモと画像をアップロードできました。

さいごに

バックエンドの部分や認証機能など簡単に設定できました。

今回はチュートリアルに沿って作成しましたが、
機能拡張や他のWEBアプリなどを作成できるようにAWSの勉強を続けていきたいと思います。