18
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【今日から始めるAWS】Amplifyを使ってReactアプリをデプロイする

Last updated at Posted at 2020-08-05

#はじめに
30代未経験からエンジニア転職をめざすコーディング初学者のYNと申します。お読みいただきありがとうございます。
今回ご紹介するAmplifyでは、AWSのサービスを組み合わせて、驚くほど簡単に、サーバレスなwebアプリをデプロイし、かつCI/CDパイプラインまで作ることができます。
本記事では、チュートリアルに従って進めることで、Amplifyの基本的な仕組みの理解を備忘録としてまとめさせていただきました。
また、チュートリアルの通りに進めると、(2020年8月現在)ビルドエラーによりデプロイが失敗しますので、対処法も併せてまとめておきたいと思います。

#今回やったこと
チュートリアルに従い、Amplifyを使って、下記AWSサービスを組み合わせてReactアプリをデプロイしました。

  • DynamoDB
  • Cognito
  • AppSync
  • S3
  • CloudFront
スクリーンショット 2020-08-02 17.39.04.png *上記アーキテクチャ図は[こちらの記事](https://xp-cloud.jp/blog/2020/03/18/6870/)から拝借いたしました。

#1. Reactアプリをデプロイし、パイプラインを作成
チュートリアル#1に従い、githubレポジトリにcommit(masterブランチにマージ)すると自動的にデプロイされるパイプラインを作ります。
Amplifyでは、CloudFrontというAWSのCDNサービスと自動的に連携して、サーバレスにwebサイトをデプロイできます。

#2. Amplifyの初期設定を行う
チュートリアル#2をに従い、Amplifyの初期設定を行います。
やることは下記3つです。

  • AmplifyCLIをローカルPCにグローバルインストール
$ npm install -g @aws-amplify/cli
$ amplify configure
  • AWSバックエンドのセットアップ
$ amplify init --appId [your-app-id]

#3. Cognitoを使ってwebページにログイン機能を実装する
チュートリアル#3をに従って進めていきます。
Amplifyを使ってReactアプリをCognitoと連携させ、驚くほど簡単にユーザー管理機能を実装することができます。

やることは下記3つです。

  • Amplifyライブラリのインストール(ルートディレクトリでnode_modulesに追加)
$ npm install aws-amplify @aws-amplify/ui-react
  • Amplifyを使って認証機能の追加
$ amplify add auth
$ amplify push --y

上記の操作により、バックエンド設定ファイルが生成され、/srcaws-exports.jsが追加されます。

  • Reactに認証機能コンポーネントを追加

aws-exports.jsをフロントエンドで読み込むことにより、AmplifyのバックエンドとReactアプリをつなぐことができます。

src/index.js
// 下記をimport
import Amplify from 'aws-amplify';
import config from './aws-exports';
Amplify.configure(config);
src/app.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

// 下記lコンポーネントをinportしてwrapする
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);

ここで実装したユーザー管理情報は、Cognitoのコンソール画面で操作することができます。

この後、リモートのmasterブランチにマージすると自動的に変更が反映されてwebページがデプロイされます。
が、この状態ではビルドエラーが発生しますので、次に対処法をまとめます。

#3-2. ビルドエラーを解決する
チュートリアルの通りに進めるとビルドエラーによりデプロイが失敗しますので、ここに対処法をまとめます。
console画面より、ビルドに関して下記3点の追加設定が必要です。

  • フロントエンドでバックエンドをデプロイする

下記のようにビルドのYML設定を追記します。
スクリーンショット 2020-08-05 11.05.28.png
上記により、フロントエンドを構築するまえに適切なバックエンド環境を構築し、内部でaws-exports.jsを生成します。これをしないと、フロントエンドのビルド時にaws-exports.jsが見つからずエラーになってしまいます。

  • フロントエンドでバックエンドリソースをデプロイするためのアクセス許可

上記のように、フロントエンドでバックエンドをデプロイするためには、ユーザーガイドに従い、サービスロールを作成する必要があります。
スクリーンショット 2020-08-05 11.44.36.png

  • ビルド設定をAmplify CLIの最新版に更新

ビルド設定のBuild image settingsを編集し、Amplify CLIのversionを最新のものに更新します。これをしないと、デフォルトのruntimeに設定されているnodeのversionが古いため、ビルドが失敗します。
スクリーンショット 2020-08-05 11.49.39.png

#4. GraphQL-APIを使ってReactアプリとデータベースを連携させる
チュートリアル#4をに従い、GraphQLの初期設定、およびデータベースとの連携を行います。

*GraohQLはfacebookが開発したwebAPIで、クエリ定義の自由度が高く、一度のリクエストで効率的に多くのデータを得ることができます。stateとの連携が容易でReactと相性がよく、とても使いやすいAPIです。
やることは下記3つです。

  • GraphQL-APIとデータベースの設定
$ amplify add api
? 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: 7 (or your preferred expiration)
? Do you want to configure advanced settings for the GraphQL API: No, I am done.
? Do you have an annotated GraphQL schema?  No
? Do you want a guided schema creation?  Yes
? What best describes your project: Single object with fields
? Do you want to edit the schema now? Yes
  • GraphQL-APIをApp-Syncにデプロイ
$ amplify push --y

上記の操作により、AWSのApp-SyncDynamo-DBのバックエンド自動的に構築されます。また、クエリーやミューテーションの設定ファイルが自動で生成されて/src/graphqlに追加されます。(すごい)

  • GraphQLをつかってReactアプリとデータベースを連携させる
src/app.js
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);

#5. S3を使ってReactアプリに写真を保存する
チュートリアル#5をに従い、ReactアプリとS3を連携させて、写真をストレージに保存できるようにします。

やることは下記3つです。

  • GraphQLスキーマの更新
amplify/backend/api/notesapp/schema.graphql

type Note @model {
  id: ID!
  name: String!
  description: String
  image: String
}
  • S3によるストレージ機能の追加
$ amplify add storage
$ amplify push --y
  • ReactアプリとS3を連携させる
src/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 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 } },
    });
  }

  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();
  }

  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);

これで、Cognito AppSync S3を組み合わせてReactアプリをデプロイすることができました。

#最後に
Amplifyを使うことで、モダンなサーバーレスアプリを簡単にデプロイすることができます。コーディング初学者には敷居が高く感じられるAWSですが、とても親近感を感じることができました。
チュートリアルには、「50分で終わる」と書かれていましたが、コーディング初心者でもその程度で出来ました。如何にAmplifyが素晴らしいサービスであるかが納得いただけるかと思います。(ただし、ビルドエラーの対処には3日かかりました。苦笑)

お読みいただきありがとうございました。

#参考にさせていただいた記事

18
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?