AWS Amplify + React で作るチャットアプリ
バージョン
モジュール | バージョン |
---|---|
node | v16.17.0 |
npm | 9.1.3 |
AWS CLI | 2.3.6 |
Amplify CLI | 10.5.1 |
TypeScript | 4.9.3 |
React | 18.2.0 |
概要
何を作るか
チャットや SNS のように、今投げた投稿が内容がすぐに画面上に反映されるような web アプリを作成する。
ざっくり要件は以下の通り。
- 投稿がすぐに画面に反映されること
- CRUD 操作のうち、C と R ができること
- 投稿が DB に保存されること
- アプリの利用に認証を必要とすること
アーキテクチャ概要
準備
事前準備
以下を利用できる環境であること。
- NodeJS
- AWS CLI
Amplify CLI のインストール(必要な場合)
以下のコマンドを実施
npm install -g @aws-amplify/cli
フロントエンド準備
CRA でフロントエンドの作成
npx create-react-app <任意のプロジェクト名> --template typescript
プロジェクト名はケバブケース (kebab-case) 推奨っぽい
--template typescript
をつけないと JavaScript 利用になっちゃうので注意。
本ケースのプロジェクト名は chat-app-demo
とした。
Amplify をフロントエンドで利用するためのモジュールインストール
CRA で作成したプロジェクトのディレクトリ直下に移動する。
cd chat-app-demo
以下コマンドを実行して、Amplify をフロントエンドで利用するためのモジュールをインストールする。
なお、インストールは yarn
を利用してもよい。
npm install aws-amplify @aws-amplify/ui-react aws-amplify-react
フロントエンドの挙動確認
以下コマンドを実行すると、開発モードでアプリが立ち上がる。
npm start
localhost:3000
で以下のような画面が表示されれば成功。
確認がとれたらctrl + c
でアプリを停止する。
AWS 準備
IAM ユーザー作成(必要な場合)
AWS マネジメントコンソール > IAM > ユーザー > ユーザーを追加 と遷移する。
以下のように入力する。
- ユーザー名: <任意>
- アクセスキー - プログラムによるアクセス: チェック
- パスワード - AWS マネジメントコンソールへのアクセス: チェックなし
「次のステップ: アクセス権限
」をクリック。
AdministratorAccess-Amplify
にチェック。
「次のステップ: タグ
」をクリック。
何もせずに「次のステップ: 確認
」をクリック。
問題なければ「ユーザーの作成
」をクリック。
「.csv のダウンロード
」をクリックして認証情報を取得しておく。
認証情報の登録
AWS CLI
、Amplify CLI
が AWS リソースにアクセスする際に利用する認証情報を登録する。
以下のコマンドを実行する。
aws configure --profile <任意のプロファイル名>
今回はプロファイル名を chat-app-demo-profile
とする。
対話形式で以下とおり 4 つの項目を入力する。
AWS Access Key ID [None]: <ダウンロードした csv に記載のアクセスキー>
AWS Secret Access Key [None]: <ダウンロードした csv に記載のシークレットアクセスキー>
Default region name [None]: ap-northeast-1
Default output format [None]: json
リージョンは任意だが今回は ap-northeast-1
とした。
なお、認証情報は以下のファイルに登録されている。
Mac
~/.aws/credencials
~/.aws/config
中身はこんな感じ
[default]
aws_access_key_id=XXX
aws_secret_access_key=XXX
[chat-app-demo-profile]
aws_access_key_id = XXX
aws_secret_access_key = XXX
[default]
region=ap-northeast-1
[profile chat-app-demo-profile]
region = ap-northeast-1
output = json
Windows
%USERPROFILE%\.aws\config
%USERPROFILE%\.aws\credentials
参考: 共有の場所configそしてcredentialsファイル
これらは必要な場面で AWS CLI、Amplify CLI から呼び出される。
Amplify による AWS リソース作成
初期設定
本コマンドでは対話形式で初期設定(初期の CloudFormation template 作成)を行う。
設定される項目や質問される事項と今回の設定値は以下の通り。
- プロジェクト名 → デフォルトでOK
- 設定は以下の通りで間違いないか → OK
設定項目 | 設定値 |
---|---|
Name | chatappdemo |
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 |
- Amplify CLI の認証方法 → AWS profile
- どのプロファイルを利用するか → chat-app-demo-profile (認証情報の登録で登録したもの)
ここまで設定されるとプロジェクトのルートディレクトリ直下にamplify
というディレクトリが作成されているのがわかる。
ここにAmplify CLI
を利用したときに追加、編集される諸々のファイルができており、例えばamplify
ディレクトリ直下の.config
ディレクトリ内に作成されているproject-config.json
には、質問 2 で確認された設定が記載されている。 -
amplify
の改善に協力するため、センシティブでない設定や失敗を共有してくれるか → 親切な人はyes
ちなみに、ここまでで amplify
ディレクトリ内のディレクトリ構成は以下の通り。
上記を踏まえた上で以下コマンドを実行しよう。
amplify init
すると以下のような対話形式で設定が進められる。
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project chatappdemo
The following configuration will be applied:
Project information
| Name: chatappdemo
| 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? (Y/n)Yes
Using default provider awscloudformation
? Select the authentication method you want to use: (Use arrow keys)
❯ AWS profile
AWS access keys
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
default
❯ chat-app-demo-profile
Adding backend environment dev to AWS Amplify app: xxx...
Deployment completed.
Deployed root stack chatappdemo [ ======================================== ] 4/4
amplify-chatappdemo-dev-12101 AWS::CloudFormation::Stack CREATE_COMPLETE Sun Dec 04 2022 01:21:42…
AuthRole AWS::IAM::Role CREATE_COMPLETE Sun Dec 04 2022 01:21:39…
UnauthRole AWS::IAM::Role CREATE_COMPLETE Sun Dec 04 2022 01:21:39…
DeploymentBucket AWS::S3::Bucket CREATE_COMPLETE Sun Dec 04 2022 01:21:30…
✔ Help improve Amplify CLI by sharing non sensitive configurations on failures (y/N) · no
Deployment bucket fetched.
✔ Initialized provider successfully.
✅ Initialized your environment successfully.
Your project has been successfully initialized and connected to the cloud!
Some next steps:
"amplify status" will show you what you've added already and if it's locally configured or deployed
"amplify add <category>" will allow you to add features like user login or a backend API
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify console" to open the Amplify Console and view your project status
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud
Pro tip:
Try "amplify add api" to create a backend API and then "amplify push" to deploy everything
記載の通り、ここまでで CloudFormation Template に追加されたリソースは以下のとおり。
- CloudFormationStack
- AuthRole
- UnauthRole
- DeploymentBucket
これらは、 amplify > backend > awscloudformation/build > root-cloudformation-stack.json
に記載されている。
authの追加
以下コマンドを実行して、 Cognito ユーザープールをテンプレートに追加する。
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?
❯ Default configuration
Default configuration with Social Provider (Federation)
Manual configuration
I want to learn more.
Warning: you will not be able to edit these selections.
How do you want users to be able to sign in? (Use arrow keys)
❯ Username
Email
Phone Number
Email or Phone Number
I want to learn more.
Do you want to configure advanced settings? (Use arrow keys)
❯ No, I am done.
Yes, I want to make some additional changes.
✅ Successfully added auth resource chatappdemoxxx 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
上記コマンドを実行すると、amplify > backend
直下に auth/chatappdemoXX..
というディレクトリが追加され、直下の build/chatappdemoXX..-cloudformation-template.json
にリソースの設定などが記載されています。
AWS 環境への反映
以下コマンドを実行して、 作成された cloudformation template を AWS 環境にデプロイすると、リソースが作成される。
amplify push -y
amplify init
で作成したリソースのスタックが作成され、amplify add auth
で作成したリソースはネストされたスタックとして作成されている。
API追加
以下コマンドを実行して、AppSync、DynamoDB をテンプレートに追加する。
amplify add api
? Select from one of the below mentioned services: (Use arrow keys)
❯ GraphQL
REST
? Here is the GraphQL API that we will create. Select a setting to edit or continue
Name: chatappdemo
❯ Authorization modes: API key (default, expiration time: 7 days from now)
Conflict detection (required for DataStore): Disabled
Continue
? Choose the default authorization type for the API
API key
❯ Amazon Cognito User Pool
IAM
OpenID Connect
Lambda
Use a Cognito user pool configured as a part of this project.
? Configure additional auth types? (y/N) N
? Here is the GraphQL API that we will create. Select a setting to edit or continue (Use arrow keys)
Name: chatappdemo
Authorization modes: Amazon Cognito User Pool (default)
Conflict detection (required for DataStore): Disabled
❯ Continue
? Choose a schema template:
Single object with fields (e.g., “Todo” with ID, name, description)
One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)
❯ Objects with fine-grained access control (e.g., a project management app with owner-based authorization)
Blank Schema
⚠️ WARNING: owners may reassign ownership for the following model(s) and role(s): PrivateNote: [owner]. If this is not intentional, you may want to apply field-level authorization rules to these fields. To read more: https://docs.amplify.aws/cli/graphql/authorization-rules/#per-user--owner-based-data-access.
✅ GraphQL schema compiled successfully.
Edit your schema at /Users/tanashoe/Desktop/work/ChatAppDemo/chat-app-demo/amplify/backend/api/chatappdemo/schema.graphql or place .graphql files in a directory at /Users/tanashoe/Desktop/work/ChatAppDemo/chat-app-demo/amplify/backend/api/chatappdemo/schema
? Do you want to edit the schema now? (Y/n) › Y
ここまで入力するとエディタで schema.graphql
というファイルが開かれる。
これを以下のように編集する。
# This "input" configures a global authorization rule to enable public access to
# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/authorization-rules
input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!
type ChatMessage
@model
@auth(
rules: [
{ allow: groups, groups: ["Users"], operations: [create, update, read, delete] }
]
) {
id: ID!
message: String!
}
pushして AWS 環境に反映しておく。
amplify push
以下のような対話で追加の設定を行う。
? Are you sure you want to continue? (Y/n) Y
⚠️ WARNING: Global Sandbox Mode has been enabled, which requires a valid API key. If
you'd like to disable, remove "input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }"
from your GraphQL schema and run 'amplify push' again. If you'd like to proceed with
sandbox mode disabled, do not create an API Key.
? Would you like to create an API Key? (y/N) › N
・・・
? Do you want to generate code for your newly created GraphQL API (Y/n) Y
? Choose the code generation language target
javascript
❯ typescript
flow
? Enter the file name pattern of graphql queries, mutations and subscriptions (src/graphql/**/*.ts) <エンター>
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions (Y/n) Y
? Enter maximum statement depth [increase from default if your schema is deeply nested] 3
? Enter the file name for the generated code (src/API.ts) <エンター>
? Do you want to generate code for your newly created GraphQL API (Y/n) Y
ここでCognitoにユーザーを作成しておく。
Cognito ユーザー作成
AWS マネジメントコンソールから Cognito > Amplify で作成したユーザープール > ユーザーを作成 と進んで、以下のように入力する(メールアドレスも入力する)。
作成後、登録したメールアドレスに、Cognito 側で自動生成されたパスワードが届くので保管しておく。
ユーザーをグループに追加
API追加の章で、GraphQL のスキーマ定義をした際に Users
グループに登録されたユーザーからの操作しか受け付けていなかったので、このユーザーを Users
グループに追加する必要がある。
Amplify で作成したユーザープール > グループ
タブ > グループを作成 と進んで、以下のように入力し、グループを作成。
ユーザー
タブから先ほど作成したユーザーを選択して、ユーザーをグループに追加 をクリック。
Users
グループにチェックして 追加 をクリック。
フロントエンドの編集
ここから、フロントエンドのコードを書いていく。
プロジェクトのルートディレクトリ直下で以下のコマンドを実行して、フロントエンドの挙動確認で実行した時と同じような画面が表示されることを確認する。
npm start
今回スタイリンングにはMUIを利用する。
以下コマンドでインストールする。
npm install @mui/material @emotion/react @emotion/styled
以下コマンドを実行することで、スキーマに対応する ChatMessage
というオブジェクトを自動生成してくれる。これをメッセージ作成の時に利用する。
amplify codegen models
生成されたオブジェクトは src > models
で確認できる。
src > App.tsx
を開く。
以下のようにコードを変更。
import React, { useState, useEffect } from "react";
import { Amplify, API, graphqlOperation } from "aws-amplify";
import { Authenticator } from "@aws-amplify/ui-react";
import awsconfig from "./aws-exports";
import "@aws-amplify/ui-react/styles.css";
import { ChatMessage as ChatMessageInstance } from "./models";
import { listChatMessages } from "./graphql/queries";
import { createChatMessage } from "./graphql/mutations";
import { onCreateChatMessage } from "./graphql/subscriptions";
import {
ListChatMessagesQuery,
OnCreateChatMessageSubscription,
ChatMessage,
} from "./API";
import Box from "@mui/material/Box";
import Chip from "@mui/material/Chip";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import { GraphQLResult } from "@aws-amplify/api";
Amplify.configure(awsconfig);
type SubscriptionEvent = {
value: {
data: OnCreateChatMessageSubscription;
};
};
const styles = {
main: {
margin: 16,
height: 504,
overflow: "auto",
},
footer: {
margin: 16,
marginLeft: 24,
height: 64,
},
message: {
margin: 8,
padding: 8,
display: "flex",
width: 300,
},
messageInput: {
width: 300,
marginRight: 8,
},
};
function App() {
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([]);
const [inputMessage, setInputMessage] = useState<string>("");
async function fetchData() {
const chatMessageData = (await API.graphql(
graphqlOperation(listChatMessages)
)) as GraphQLResult<ListChatMessagesQuery>;
if (chatMessageData.data?.listChatMessages?.items) {
const messages = chatMessageData.data.listChatMessages
.items as ChatMessage[];
setChatMessages(sortMessage(messages));
}
}
function sortMessage(messages: ChatMessage[]) {
return [...messages].sort(
(a, b) =>
new Date(a.createdAt!).getTime() - new Date(b.createdAt!).getTime()
);
}
async function saveData() {
const model = new ChatMessageInstance({
message: inputMessage,
});
await API.graphql(
graphqlOperation(createChatMessage, {
input: model,
})
);
setInputMessage("");
}
function onChange(messge: string) {
setInputMessage(messge);
}
useEffect(() => {
fetchData();
const onCreate = API.graphql(graphqlOperation(onCreateChatMessage));
if ("subscribe" in onCreate) {
const subscription = onCreate.subscribe({
next: ({ value: { data } }: SubscriptionEvent) => {
const newMessage: ChatMessage = data.onCreateChatMessage!;
setChatMessages((prevMessages) =>
sortMessage([...prevMessages, newMessage])
);
},
});
return () => {
subscription.unsubscribe();
};
}
}, []);
return (
<Authenticator>
{({ signOut, user }) => (
<main>
<h2>Hello {user?.username}</h2>
<button onClick={signOut}>Sign out</button>
<Box style={styles.main}>
{chatMessages &&
chatMessages.map((message, index) => {
return (
<Chip
key={index}
label={message.message}
color="primary"
style={styles.message}
/>
);
})}
</Box>
<Box style={styles.footer}>
<TextField
variant="outlined"
type="text"
color="primary"
size="small"
value={inputMessage}
style={styles.messageInput}
onChange={(e) => onChange(e.target.value)}
placeholder="メッセージを入力"
/>
<Button
variant="contained"
color="primary"
onClick={() => saveData()}
>
投稿
</Button>
</Box>
</main>
)}
</Authenticator>
);
}
export default App;
エラーが出ていなければ、localhost:3000
で実行されているアプリは以下のような画面を表示しているはず。
ここに[Cognito ユーザー作成](#Cognito ユーザー作成)で追加したユーザーのユーザーネームと、登録したメールアドレスに届いたパスワードを入力する。
するとパスワード変更画面に遷移するので、任意のパスワードに変更する。
変更して再アクセスすると以下のような画面が表示されている。
自由にメッセージを入力して「投稿
」をクリックすると、画面にメッセージが即時反映される。
以上でチャットアプリ作成の Phase.1 を完了とする。
メモ
基本的な型定義は全て API.ts
に記述されている。
なのでメッセージに型をつける場合、利用するのは、API.ts
で定義されているもの。
amplify codegen models
で生成されるのは、ChatMessage
クラスで、メッセージ作成時に利用する。
出力されたエラー
内部で色々使ってるみたいだけど、たぶん MUI コンポーネント内の style タグでスタイリングしてるからだと思う。
Failed to compile.
Module not found: Error: Can't resolve '@emotion/react' in '/Users/tanashoe/Desktop/work/ChatAppDemo/chat-app-demo/node_modules/@mui/styled-engine/GlobalStyles'
WARNING in [eslint]
src/App.tsx
Line 114:6: React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps
ERROR in ./node_modules/@mui/styled-engine/GlobalStyles/GlobalStyles.js 3:0-40
Module not found: Error: Can't resolve '@emotion/react' in '/Users/tanashoe/Desktop/work/ChatAppDemo/chat-app-demo/node_modules/@mui/styled-engine/GlobalStyles'
ERROR in ./node_modules/@mui/styled-engine/StyledEngineProvider/StyledEngineProvider.js 3:0-47
Module not found: Error: Can't resolve '@emotion/react' in '/Users/tanashoe/Desktop/work/ChatAppDemo/chat-app-demo/node_modules/@mui/styled-engine/StyledEngineProvider'
ERROR in ./node_modules/@mui/styled-engine/index.js 7:0-39
Module not found: Error: Can't resolve '@emotion/styled' in '/Users/tanashoe/Desktop/work/ChatAppDemo/chat-app-demo/node_modules/@mui/styled-engine'
ERROR in ./node_modules/@mui/styled-engine/index.js 35:0-62
Module not found: Error: Can't resolve '@emotion/react' in '/Users/tanashoe/Desktop/work/ChatAppDemo/chat-app-demo/node_modules/@mui/styled-engine'
ERROR in ./node_modules/@mui/system/esm/ThemeProvider/ThemeProvider.js 11:27-60
export 'ThemeContext' (imported as 'StyledEngineThemeContext') was not found in '@mui/styled-engine' (possible exports: GlobalStyles, StyledEngineProvider, default, internal_processStyles)
ERROR in ./node_modules/@mui/system/esm/index.js 1:0-88
export 'css' (reexported as 'css') was not found in '@mui/styled-engine' (possible exports: GlobalStyles, StyledEngineProvider, default, internal_processStyles)
ERROR in ./node_modules/@mui/system/esm/index.js 1:0-88
export 'keyframes' (reexported as 'keyframes') was not found in '@mui/styled-engine' (possible exports: GlobalStyles, StyledEngineProvider, default, internal_processStyles)
webpack compiled with 7 errors and 1 warning
Files successfully emitted, waiting for typecheck results...
Issues checking in progress...
No issues found.
以下をインストールして解消。
- @emotion/styled
- @emotion/react
参照サイト様
基本的な進め方: Amplifyで簡単に作れるリアルタイムチャット機能
API リクエストへの型付け: AmplifyのAPIリクエストをTypeScriptでちゃんと型をつける
公式チュートリアル: Amplify Dev Center Tutorial
GraphQL API について: Amplify Dev Center API(GraphQL)
Discussion
こんにちは。
この部分、
new ChatMessageInstance(...)
としてしまうと私の環境ではエラーとなってしまいました。以下のように単なるオブジェクト定義にしたらおそらく狙い通りに動きました。