react+amplifyで、簡単なメモアプリ(管理者ユーザあり)を開発してみた。reactのcognitoユーザ認証のやり方が、色々と変更されているので注意!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# reactプロジェクト生成 npx create-react-app admin-memo # reactプロジェクト内に、amplifyプロジェクト生成 cd admin-memo amplify init # reactとユーザ認証に必要なパッケージをインストール npm install aws-amplify @aws-amplify/ui-react npm install @aws-amplify/auth @aws-amplify/api # ユーザ認証を追加 amplify add auth # AppSync(DynamoDBのAPIサーバ)を追加(メモのデータを管理する) amplify add api # 認証にはCognitoを使う(デフォルトはAPIキー) # Choose the default authorization type for the API Amazon Cognito User Pool # ユーザ認証でアクセスコントロールしたいので、Objects with fine-grained access controlを選択だけど # 結局、上書きするので、Blank Schemaでもなんでもいい。 ? 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 |
テーブル定義と権限を設定
amplify\backend\api\adminmemo\schema.graphql
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
type Memo @model @auth( rules: [ { allow: owner } # メモの作成者(owner)のみがCRUD可能 { allow: groups, groups: ["Admin"], operations: [read, update, delete] } # Adminは全メモを管理できる ] ) { id: ID! title: String! content: String! owner: String! @auth( rules: [ { allow: owner, operations: [create, read] } #更新は出来ない。 { allow: groups, groups: ["Admin"], operations: [read] } # Admin に read 権限を追加 ] ) } |
バックエンドの設定は出来たので、AWS反映
1 |
amplify push |
フロント側をreactで作る
src/MemoList.js // 自分のメモ一覧
src/CreateMemo.js // メモ作成画面
src/AdminPanel.js // 管理画面
sr/App.js // トップ画面
ユーザ認証が、auth v6からやり方が変更されているので注意!!
import { Auth } from ‘@aws-amplify/auth’; // ✅ 修正済み
auth じゃなくて、generateClientだったり
import { generateClient } from ‘aws-amplify/api’; // ✅ v6 用の API クライアント
ユーザの所属グループが、fetchAuthSessionじゃないと取得できなかったり・・・。
src/MemoList.js // 自分のメモ一覧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import { useEffect, useState } from 'react'; import { generateClient } from 'aws-amplify/api'; import { listMemos } from './graphql/queries'; const client = generateClient(); const MemoList = () => { const [memos, setMemos] = useState([]); useEffect(() => { const fetchMemos = async () => { const result = await client.graphql({ query: listMemos }); setMemos(result.data.listMemos.items); }; fetchMemos(); }, []); return ( <div> <h2>メモ一覧</h2> <ul> {memos.map((memo) => ( <li key={memo.id}>{memo.title}</li> ))} </ul> </div> ); }; export default MemoList; |
src/CreateMemo.js // メモ作成画面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
import { useState } from 'react'; import { generateClient } from 'aws-amplify/api'; import { fetchAuthSession } from '@aws-amplify/auth'; // ✅ ユーザー情報を取得するために追加 import { createMemo } from './graphql/mutations'; const client = generateClient(); const CreateMemo = () => { const [title, setTitle] = useState(''); const [content, setContent] = useState(''); const handleSubmit = async () => { const trimmedTitle = title.trim(); const trimmedContent = content.trim(); if (!trimmedTitle || !trimmedContent) { alert("タイトルと内容を入力してください!"); return; } try { // ✅ 現在のユーザーを取得 const session = await fetchAuthSession(); const idToken = session.tokens?.idToken; const username = idToken?.payload["cognito:username"]; // ✅ `owner` を取得 if (!username) { console.error("ユーザー名が取得できませんでした"); alert("認証エラー: ログインし直してください"); return; } // ✅ `owner` を含めて GraphQL に送信 await client.graphql({ query: createMemo, variables: { input: { title: trimmedTitle, content: trimmedContent, owner: username } }, }); // フォームをリセット setTitle(""); setContent(""); } catch (error) { console.error("GraphQL エラー:", JSON.stringify(error, null, 2)); alert("メモ作成に失敗しました"); } }; return ( <div> <h2>メモを作成</h2> <input type="text" placeholder="タイトル" value={title} onChange={(e) => setTitle(e.target.value)} /> <textarea placeholder="内容" value={content} onChange={(e) => setContent(e.target.value)} /> <button onClick={handleSubmit}>作成</button> </div> ); }; export default CreateMemo; |
src/AdminPanel.js // 管理画面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import { useEffect, useState } from 'react'; import { generateClient } from 'aws-amplify/api'; import { listMemos } from './graphql/queries'; import { deleteMemo } from './graphql/mutations'; const client = generateClient(); const AdminPanel = () => { const [memos, setMemos] = useState([]); useEffect(() => { const fetchMemos = async () => { const result = await client.graphql({ query: listMemos }); setMemos(result.data.listMemos.items); }; fetchMemos(); }, []); const handleDelete = async (id) => { await client.graphql({ query: deleteMemo, variables: { input: { id } }, }); setMemos(memos.filter((memo) => memo.id !== id)); }; return ( <div> <h2>管理者パネル</h2> <ul> {memos.map((memo) => ( <li key={memo.id}> {memo.title} <button onClick={() => handleDelete(memo.id)}>削除</button> </li> ))} </ul> </div> ); }; export default AdminPanel; |
sr/App.js // トップ画面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
import { useEffect, useState } from 'react'; import { withAuthenticator } from '@aws-amplify/ui-react'; import { fetchAuthSession, signOut } from '@aws-amplify/auth'; // fetchAuthSessionじゃないとgroupsが取得できない import AdminPanel from './AdminPanel'; import CreateMemo from './CreateMemo'; import MemoList from './MemoList'; import { Amplify } from 'aws-amplify'; import awsExports from './aws-exports'; Amplify.configure(awsExports); const App = ({ user }) => { const [isAdmin, setIsAdmin] = useState(false); const [userGroups, setUserGroups] = useState("なし"); const [UserEmail, setUserEmail] = useState("なし"); useEffect(() => { const fetchUserGroups = async () => { try { // ✅ `fetchAuthSession()` を使い、最新のセッション情報を取得 const session = await fetchAuthSession({ forceRefresh: true }); const idToken = session.tokens?.idToken; // ✅ `cognito:groups` の取得 const groups = idToken?.payload?.['cognito:groups'] || []; const UserEmail = idToken?.payload?.["email"] || "メール未登録"; console.log("ログイン中のグループ:", groups); setUserEmail(UserEmail); setUserGroups(groups.length ? groups.join(', ') : "なし"); setIsAdmin(groups.includes("Admin")); // Admin グループにいるか判定 } catch (error) { console.error("グループ情報の取得エラー:", error); } }; fetchUserGroups(); }, []); return ( <div> <h1>メモアプリ</h1> <p>ログイン中: {UserEmail}</p> <p>グループ: {userGroups}</p> <button onClick={async () => { await signOut(); window.location.reload(); }}>ログアウト</button> {isAdmin ? <AdminPanel /> : <><CreateMemo /><MemoList /></>} </div> ); }; export default withAuthenticator(App); |
フロントエンドを、ローカルで動作確認(バックエンドのcognitoやAppSyncは、本物のAWSを使っている)
1 |
npm start |
http://localhost:3000/
こんな感じのログイン画面になるので、右上のCreate Acountタブからユーザ登録する。
Email認証があるので、受信できるメールアドレスが必要。
問題なく動作確認できたら、hosting & deployしてWeb公開する。
1 2 |
amplify add hosting amplify publish |
こうやってみると、バックエンドはコマンド一発で、フロントエンドのコーディングしかしてない。
まさにフロントエンドエンジニア!って感じ。