前回、Amplify Gen1 で、DBへの登録と読み込みを試した。同じようにAmplify Gen2 で構築するとどうなるのか試す。前回と同じ順番で認証方式を試していく。

以下のドキュメントをベースに進める。
■Amplify Docs (Gen 2) – AWS Amplify Gen 2 Documentation
https://docs.amplify.aws/gen2/
Amplify Gen2 セットアップ
下記のGen2ドキュメントの通り、Next.js(App Router)の Client Components の初期セットアップを行う。
■Next.js App Router (Client Components) – AWS Amplify Gen 2 Documentation
https://docs.amplify.aws/gen2/start/quickstart/nextjs-app-router-client-components/
npm create next-app@14 -- next-amplify-gen2 --typescript --eslint --app --no-src-dir --no-tailwind --import-alias '@/*'
cd next-amplify-gen2
npm create amplify@beta
以下はGen1と同様である。
npm run dev
Gen1との違いは、ローカル環境でSandboxを起動する点。別のターミナルを立ち上げ、上記と並行して実行状態とする。
npx amplify sandbox
Sandboxを起動するとCloudFormationが実行され、AWS AppSync、DynamoDB、AWS Lambda、Amazon Cognito などが自動生成される。Sandbox起動時の流れは以下になる。

以下のようなCloudFormationが生成される。





バックエンド構築
Gen2ドキュメントの通り、 amplify/data/resource.ts を修正してデプロイする。最低限書き込み・読み込みができるようにし、詳細の検証は次回に回す。認証方式はuserPool、ログイン必須で自分の書き込んだTodoのみ読み込むことができる。doneとpriorityのカラムも追加している。
const schema = a.schema({
Todo: a
.model({
content: a.string(),
done: a.boolean(),
priority: a.enum(['low', 'medium', 'high'])
})
.authorization([a.allow.owner()]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'userPool'
},
});
上記テキストを保存すると自動的にSandboxがデプロイされる。
UIの構築
ドキュメント通り、以下のコマンドを実行する。
npm install @aws-amplify/ui-react
初期処理を実行するファイルを新規作成する。
// components/ConfigureAmplify.tsx
"use client";
import config from "@/amplifyconfiguration.json";
import { Amplify } from "aws-amplify";
Amplify.configure(config, { ssr: true });
export default function ConfigureAmplifyClientSide() {
return null;
}
メイン処理である /app/page.tsx の実行前に必ず呼ばれる /app/layout.tsx を修正する。先ほど作成したコンポーネントを呼び出し、初期処理を実行する。あと細かいが、画面が真っ黒で見た目があまりよくないため、globals.css は呼び出さないようにしている。
// app/layout.tsx
import ConfigureAmplifyClientSide from "@/components/ConfigureAmplify";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
//import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<ConfigureAmplifyClientSide />
{children}
</body>
</html>
);
}
順番が前後するが先にTodoListコンポーネント(CSR)を新規作成しておく。細かいが一部スタイルを適用し、0件時の処理を加えている。useEffect() にてlistTodos()を呼び出してもよいが、Create時に追加したTodoが表示されないため、subscribeという機能を使って、DBに値が入ると自動表示されるようにしている。例えば別タブで同じ画面を開きTodoをCreateした場合、元の画面でもその別タブで追加したTodoが(リロードしなくても)自動で表示される。
これはGraphQLを使っていると享受できる大きなメリットである。この仕組みを使うことで、例えば簡単なチャットページであればすぐに作れてしまう(ある特定の条件でテーブルを監視するだけでよい)。
// components/TodoList.tsx
"use client";
import { useState, useEffect } from "react";
import { generateClient } from "aws-amplify/data";
import type { Schema } from "@/amplify/data/resource";
// generate your data client using the Schema from your backend
const client = generateClient<Schema>();
export default function TodoList() {
const [todos, setTodos] = useState<Schema["Todo"][]>([]);
async function listTodos() {
// fetch all todos
const { data } = await client.models.Todo.list();
setTodos(data);
}
useEffect(() => {
//listTodos();
// Create a couple of to-dos, then refresh the page to see them.
// You can also subscribe to new to-dos in your useEffect to have them live reload on the page.
const sub = client.models.Todo.observeQuery().subscribe(({ items }) =>
setTodos([...items])
)
return () => sub.unsubscribe()
}, []);
return (
<div>
<h1>Todos</h1>
<button onClick={async () => {
// create a new Todo with the following attributes
const { errors, data: newTodo } = await client.models.Todo.create({
// prompt the user to enter the title
content: window.prompt("title"),
done: false,
priority: 'medium',
})
}}>Create </button>
<ul style={{ paddingLeft: '0px' }}>
{todos.length > 0 ?
todos.map((todo) => (
<li key={todo.id} style={{ listStyle: 'none' }}>{todo.content}</li>
))
: ''
}
</ul>
</div>
);
}
呼び出し側の/page.tsxは以下の通り。見た目を整えるためメイン領域はセンタリングしておく。今回、userPoolを採用するため、withAuthenticatorを使ってログイン必須としている(ログインしていないと一切アクセスできないため)。
// app/page.tsx
"use client";
import { withAuthenticator } from "@aws-amplify/ui-react";
import "@aws-amplify/ui-react/styles.css";
import TodoList from "@/components/TodoList";
function App() {
return (
<div
style={{
maxWidth: '500px',
margin: '0 auto',
textAlign: 'center',
marginTop: '100px'
}}
>
<h1>Hello, Amplify 👋</h1>
<TodoList />
</div>
);
}
export default withAuthenticator(App);
このような画面が表示されるはずである。

次回はIAM認証方式での対応を行う。