AWSCode

AWS 公式チュートリアル「React アプリケーションの構築」をやってみた

AWS

今回はAWS Amplifyを学習するために以下のAWA公式のチュートリアルをやってみました。

AWS でフルスタック React アプリケーションを構築
フロントエンドデベロッパーがフルスタックの React ウェブアプリケーションを数分で構築してデプロイするためのチュートリアル

チュートリアルが作成されたのが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】Amplifyを使ってReactアプリをデプロイする - Qiita
#はじめに30代未経験からエンジニア転職をめざすコーディング初学者のYNと申します。お読みいただきありがとうございます。今回ご紹介するAmplifyでは、AWSのサービスを組み合わせて、驚くほど…

フロントエンドを構築する前に適切なバックエンド環境を構築し、内部で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について確認
    • listNotescreateNotedeleteNoteなどの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用語や構成に関しても習得できているはずです。

AWS認定ソリューションアーキテクト-アソシエイト問題集

今回は以上となります。

コメント