今回はAWS Amplifyを学習するために以下のAWA公式のチュートリアルをやってみました。
チュートリアルが作成されたのが1年以上前のようで、そのまま進めると幾つかはまる部分があったのでメモしておきます。
また、チュートリアル内では確認部分が少なかったりしたので補足しています。
はじめに
このチュートリアルは 5 つの短いモジュールに分かれています。
最終的にはCognito認証がされている、画像アップができるノートアプリを作成します。
それぞれで違う部分やはまった部分について記載します。なお、私はオレゴン(us-west-2)リージョンで試してます。
モジュール1:React アプリケーションをデプロイしてホストする
フロントエンド側のアプリをGithubと連携してCI/CDでデプロイまで実施しています。
CloudFrontとか意識する必要もないので、めちゃくちゃ簡単ですね。
ここは特にハマる部分はなかったです。ただ、Githubでパスワード認証が使えなくなっていたので対応しました。以下の記事を参考にどうぞ。
モジュール2: ローカル Amplify アプリを初期化する
バックエンドを構築してます。
amplifyコマンドを実行するだけで簡単にバックエンド(この時点では何もないはず。。。)が作成できました。簡単すぎて何をやっているかが具体的に見えないのはある意味問題がある気がしますね。。。
問題2-1:amplify init コマンドがない
チュートリアルでは、`[バックエンド環境] タブで、amplify init を コマンドをキーボードにコピーする、となっていますがAmplifyマネコンの画面が異なっていてできませんでした。
対応2-1:マネコン経由で手動で作成してからamplify pullする
そのため、いったんコンソールにあるボタン経由でBackendを作成してから、以下の様な画面に表示されているPullコマンドをローカル側から実行しました。
もしかしたらフロントエンドからappID
を取得してinitしてもよかったのかもしれません。
amplify pull --appId XXXXXX --envName staging
pullの途中でAdmin UI management
にログオンを求められます。
このAdmin UI management
はIAMアカウントとは関係なく、事前にユーザ(メールアドレス)をマネコン経由で招待する必要があります。
PS C:\Project\amplifyapp> amplify pull --appId XXXXXX --envName staging
Opening link: https://us-west-2.admin.amplifyapp.com/admin/XXXXXX/staging/verify/
√ Successfully received Amplify Admin tokens.
Amplify AppID found: d2edmx11tq0or6. Amplify App name is: test-amplifyapp
Backend environment staging found in Amplify Console app: test-amplifyapp
? 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 react
? Source Directory Path: src
? Distribution Directory Path: build
? Build Command: npm.cmd run-script build
? Start Command: npm.cmd run-script start
? Do you plan on modifying this backend? Yes
√ Successfully pulled backend environment staging from the cloud
また、モジュールのまとめにあるamplify console
コマンドを実行するとダッシュボードで表示される、とありますが、Admin UIかconsoleのどちらを開くか選択が必要でした。
PS C:\Project\amplifyapp> amplify console
? Which site do you want to open? ...
> Amplify admin UI
Amplify console
モジュール3:認証を追加する
ここではCognitoの認証を追加しています。これまた1コマンドだけで簡単にできるのがすごいですね。
それもローカルでのstaging
環境と公開されているmain
環境をすでに分離した状態でそれぞれ認証プールが作成されています。
問題3-1:aws-amplify ライブラリをインストール時にエラー
aws-amplify ライブラリをインストール時に以下のエラーが表示されました。
PS C:\Project\amplifyapp> npm install aws-amplify @aws-amplify/ui-react
152 packages are looking for funding
run `npm fund` for details
found 31 vulnerabilities (12 moderate, 18 high, 1 critical)
run `npm audit fix` to fix them, or `npm audit` for details
対応3-1:npm audit fixを実行して対応
エラーメッセージに表示されている通りにコマンドを実行したら解決しました。依存関連?のエラーなのかな?
npm audit fix
問題3-2:ローカルでは問題なく表示されるが、Amplifyにデプロイするとビルドでエラーになる。
ビルドログを見ると以下のようにaws-exportsが存在しないと言われている。
[INFO]: ./src/index.js
Cnnot find file './aws-exports' in './src'.
[WARNING]: error Command failed with exit code 1.
[INFO]: info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
[ERROR]: !!! Build failed
対応3-2:ビルドエラーを解消する
調べたところ以下のサイトで対応方法が記載されていました(ありがとうございます!)
フロントエンドを構築する前に適切なバックエンド環境を構築し、内部でaws-exports.js
を生成することエラーを回避します。とありますが、私は理解ができていないです。
①ビルドの設定の部分にbackendを追加する
version: 1
backend:
phases:
build:
commands:
- '# Execute Amplify CLI with the helper script'
- amplifyPush --simple
frontend:
phases:
preBuild:
commands:
- yarn install
build:
commands:
- yarn run build
artifacts:
baseDirectory: build
files:
- '**/*'
cache:
paths:
- node_modules/**/*
②BackendへのアクセスするRoleを作成、Amplifyへ付与する
IAMからRoleを作成します。
Roleが作成できたらAmplifyアプリのService role
に設定します。
③ビルド設定をAmplify CLI
の最新版に更新
ビルドの設定に以下の設定を追加してAmplify CLIを最新版に更新します。
確認:正しくCofnito認証が追加されたことを確認する
正常にデプロイができたら動作確認をしたいと思います。
まず、ページにアクセスして認証画面ができることを確認します。
次に実際にユーザーを登録してみます。入力したメールアドレス宛にverifiction code
が届きますので入力します。
認証が成功して、以下の様な画面が表示されれば問題ないです。
また、マネコン経由でCognitoを確認すると先ほどのユーザーが登録されていることを確認することができます。
- 補足:認証部分について
- @aws-amplify/ui-reactライブラリから withAuthenticator, AmplifySignOutを読み込んで利用している模様
<AmplifySignOut />
でサインアウトボタンが表示されているらしいexport default withAuthenticator(App);
だけで認証がなければ弾くようになっている模様。すごい簡単。。。
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { withAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react'
function App() {
return (
<div className="App">
<header>
<img src={logo} className="App-logo" alt="logo" />
<h1>We now have Auth!</h1>
</header>
<AmplifySignOut />
</div>
);
}
export default withAuthenticator(App);
モジュール4:GraphQL API とデータベースを追加する
- メモ:少しだけ質問される内容が異なっていますが、気にせず進めれるレべルです。
PS C:\Project\amplifyapp> amplify add api
? Please select from one of the below mentioned services: (Use arrow keys)
? Please select from one of the below mentioned services: GraphQL
? Provide API name: notesapp
? Choose the default authorization type for the API API key
? Enter a description for the API key: demo
? After how many days from now the API key should expire (1-365): 7
? 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)
The following types do not have '@auth' enabled. Consider using @auth with @model
- Todo
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/auth
GraphQL schema compiled successfully.
Edit your schema at C:\Project\amplifyapp\amplify\backend\api\notesapp\schema.graphql or place .graphql files in a directory at C:\Project\amplifyapp\amplify\backend\api\notesapp\schema
? Do you want to edit the schema now? Yes
Edit the file in your editor: C:\Project\amplifyapp\amplify\backend\api\notesapp\schema.graphql
Successfully added resource notesapp 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
問題4-1:npm start実行時にコンパイルエラーが発生
Failed to compile.
./src/App.js
Attempted import error: 'createNote' is not exported from './graphql/mutations' (imported as 'createNoteMutation').
対応4-1:schema.graphqlの記載誤り
デフォルトのtodoのままだったのでエラーになった。修正してから再度amplify pushして解決。完全に自分のミス
type Note @model {
id: ID!
name: String!
description: String
}
- 補足:App.jsについて確認
listNotes
やcreateNote
、deleteNote
などのCRUDの動作をGraphqlから読み込んで利用している、っぽい- react勉強しないと理解がなかなかできんな、と感じた。
import React, { useState, useEffect } from 'react';
import './App.css';
import { API } from 'aws-amplify';
import { withAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react';
import { listNotes } from './graphql/queries';
import { createNote as createNoteMutation, deleteNote as deleteNoteMutation } from './graphql/mutations';
const initialFormState = { name: '', description: '' }
function App() {
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>
<AmplifySignOut />
</div>
);
}
export default withAuthenticator(App);
確認:
npm start
でアプリを起動して、(以前にやっていなければ)ユーザー登録をしてからログオンして、Cognitoによるユーザー認証がかかっていることを確認する。
My Notes Appメモアプリが表示されることを確認して、ノートが登録/削除できることを確認する。
モジュール5:ストレージを追加する
おまけとしてストレージを作成して、ノートに画像を追加できるようにする。画像の保存場所としてS3を利用している。
- メモ:例のごとく少し表示が異なる
PS C:\Project\amplifyapp> amplify add storage
? Please select from one of the below mentioned services: Content (Images, audio, video, etc.)
? Please provide a friendly name for your resource that will be used to label this category in the project: imagestorage
? Please provide bucket name: test-amplify-app-20211112
? 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? No
- 補足:このモジュールでは変更点しかのってないので最終的なApp.jsを記載
import React, { useState, useEffect } from "react";
import "./App.css";
import { API, Storage } from 'aws-amplify';
import { withAuthenticator, AmplifySignOut } from "@aws-amplify/ui-react";
import { listNotes } from "./graphql/queries";
import {
createNote as createNoteMutation,
deleteNote as deleteNoteMutation,
} from "./graphql/mutations";
const initialFormState = { name: "", description: "" };
function App() {
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>
<AmplifySignOut />
</div>
);
}
export default withAuthenticator(App);
確認
npm start
でアプリを起動して、ノートに画像のアップロードができることを確認します。
S3側のバケットにも画像が保存されていることが確認できます。(ただし、ノートを削除しても画像は残るみたいです)
DynamoDBにもデータが登録されていることを確認します。
AppSyncでAPIが作成されており、スキーマが以下の通り存在することを確認します。
- 補足:チュートリアルではこれで削除して終了となりますが、githubにpushして本番系のamplify appも正しくデプロイできていること(xxx-mainの各リソースも作成されていること)を確認します。
リソース削除
以下コマンドでstaging環境が削除できる
amplify delete
あとは、frontend側のアプリとバックエンドのmainをマネコンamplifyから手動で削除する。
Cfnのスタックから削除しないこと。
AWSを効率的に学習する方法
私が効率的にAWSを学習するために実施した方法は以下の通りです。
①最初に書籍(ハンズオンができる)を購入、座学でAWSの基礎を学習
②AWS資格試験を取得ための学習
※私の場合は①と②を合わせて2か月でソリューションアーキテクトを取得できました。
①AWS基礎学習
最初に購入した書籍は「Amazon Web Services 基礎からのネットワーク&サーバー構築」です。
Amazon Web Services 基礎からのネットワーク&サーバー構築
この本では、AWSの基本サービスを利用したハンズオンを通じて、AWSの基礎を学習することができます。
また、タイトル通りAWSのネットワークやインフラに関しても網羅しているため、もともとインフラ系の技術者ではない人たちにとっても分かりやすい内容だと思います。
とりあえずAWS上にサーバーを設定して開発を行うための準備までするには最良の一冊です。
Amazon Web Services 基礎からのネットワーク&サーバー構築
②AWS資格取得
AWSの基礎をある程度学習することができたら、次はAWS資格を取得しましょう。まずはAWSソリューションアーキテクトを目指しましょう。
資格勉強のための問題集をひたすら解きながら、AWSの知識を積み重ねて習得していきましょう!
苦行ではありますが、この問題集を3周ほどすればAWS用語や構成に関しても習得できているはずです。
今回は以上となります。
コメント