AWS Amplify Gen2についてわかりやすくまとめてみた
はじめに
Amplify Gen2のプレビュー版が正式リリース版(GA)になりましたので、
今回は従来のAmplify CLI による開発のことをGen1と呼び、Gen2との違いをまとめてみました。
なお、Amplify Gen2では、デフォルトで後述するSandbox環境とProduction環境の2つの CloudFormationスタックを作成しますので削除しないようにしてください。
後半では、Amplify Gen2のNext.js(App Router)のチュートリアルを実際に実行し、次のようなCRUD操作ができるTodoリストを作成して新機能について確認しています。
Amplify CLIによる開発(Gen1)については下記の記事を参考にしてください。
Amplify Gen 2とは
TypeScriptを中心とした、AWSのサービスを活用してフロントエンド開発者がバックエンドの複雑さを気にせずに、クラウドを活用してアプリを作れるようサポートするフレームワークです。
AWS Amplify Gen1とGen2の違い
実行コマンドの変更
実行コマンドがamplify
からampx
に変更になりました。
amplify_outputs.jsonの生成
Gen2では、CLIがamplify_outputs.json
というファイルを生成します。
このファイルには、データエンドポイントや認証メタデータなどのバックエンド出力が含まれ、クライアントライブラリの設定に使用されます。
ローカルでの開発では、ampx sandbox
を使うことでこのファイルが作成されます。
CI/CDとの統合
Gen1
Gen1では、開発者がバックエンドリソースに変更を加えた場合、その変更をクラウドにデプロイするためには手動で以下のステップを踏む必要がありました。
デプロイ手順
ローカルでコードを変更
→CLIを使用して、amplify push
コマンドを実行し、変更をAWSにデプロイ。
Gen2
Gen2では、git push
の実行だけでバックエンドが自動的にビルド、テスト、デプロイされるようになりました。
デプロイ手順
ローカルでコードを変更
→変更をGitリポジトリにコミット&リモートリポジトリにプッシュ
(この後は自動で行われます)
→プッシュをトリガーとして、自動的にAWS Amplifyがバックエンドをビルドし、統合テストを実行。
→テストがパスすれば、自動的に本番環境または指定されたステージング環境にデプロイ。
この自動化されたプロセスにより、手動でデプロイメントを行う手間が省け、より頻繁かつ安全にアプリケーションのアップデートが可能になります。
バックエンドリソースのデプロイ方法
Gen1
CLIを使用します。
Gen1
CDKを使用します。
この変更により、Amplifyがデフォルトで提供する機能を超える機能が必要な場合、CDKを追加して対応することができるようになりました。
データベース操作、API設定
Gen1
GraphQLスキーマやAPI設定は、schema.graphql
(amplify/backend/api/<Amplifyのプロジェクト名>/schema.graphql
)ファイルに記述していました。
Gen2
amplify/data/resource.ts
ファイルにデータベースのスキーマやAPIの設定を定義することが可能になりました。
直接GraphQLで記述するのではなく、TypeScriptを使用するので型安全性やIDE(VSCodeなど)の自動補完機能などの恩恵を受けることができ、効率的に作業を進めることができます!
バックエンドリソースの変更確認
文章中に出てくるサンドボックス環境については、後で説明しています。
Gen1
ローカル環境で認証機能の設定やその他のバックエンドリソースの変更を行った場合、これらの変更をローカルで確認するにはamplify push
を実行してクラウドにデプロイする必要があります。
Gen1ではサンドボックス環境が提供されていないため、クラウドリソースの安全な管理、整合性の維持、および開発環境と本番環境の適切な分離を保証するためにこのプロセスが必要です。
Gen2
サンドボックス環境が使用できるようになりました。
サンドボックス環境(Sandbox)
Sandboxとは、本番環境に移行する前にバックエンドのリソースをテストできる一時的な環境です。
Gen1
バックエンドの変更をクラウド環境に反映させるためには、amplify push
コマンドを実行(手動デプロイメント)する必要があり、ローカルでの即時の変更反映がサポートされていないため、開発者はデプロイ後にのみ変更内容を確認できました。
Gen2
Gen2で追加されたサンドボックス環境では、ローカルでの開発環境(npm run dev
を実行している環境)でバックエンドの変更(例えば認証機能の追加など)をコードの変更が自動的に検知され、amplify push
コマンドを実行することなく、サンドボックス内で即座に反映されます。
リアルタイムで変更を確認することができるので、より迅速にテストと調整を行うことが可能になりました。
また、各開発者が独立したサンドボックス環境を持つことができるようになっており、他の開発者の作業に影響を与えることなくテストや開発を行うことが可能になりました。
チュートリアル
今回は、Next.js(App Router)チュートリアルを参考にGen2の機能について確認したいと思います。
テンプレート
からリポジトリを作成する
を選択してください。
Private
を選択し、Create repository
を選択してください。
AWSへのデプロイ
を選択します。
GitHub
を選択し、次へ
を押してください。
先ほど作成したリポジトリを選択したら、次へ
を押してください。
次へ
を押してください。
保存してデプロイ
を押してください。
デプロイされるまで少し待ってください。
デプロイが終わって、デプロイ済みと表示されたらドメインを確認してください。
全般設定 > サービスロールで確認できますが、Amplify...
というロールも作成されています。
ToDoアプリが表示されていると思います。何か登録してみてください。
main
を選択、
左のメニューから データ > データマネージャーを選択すると登録した内容が確認できます。
リポジトリをローカルのマシンに複製する
リポジトリのクローンをローカルに作成します。
git clone https://github.com/<github-user>/amplify-next-template.git
cd amplify-next-template && npm install
デプロイ > デプロイされたバックエンドリソース
Download amplify_outputs.json file
を選択してください。
ダウンロードしたamplify_outputs.json
ファイルをプロジェクトのルートディレクトリに貼り付けてください。
削除機能を追加する
app/page.tsx
の内容を次のように変更することで、削除機能を追加することができます。
'use client';
import type { Schema } from '@/amplify/data/resource';
import outputs from '@/amplify_outputs.json';
import '@aws-amplify/ui-react/styles.css';
import { Amplify } from 'aws-amplify';
import { generateClient } from 'aws-amplify/data';
import { useEffect, useState } from 'react';
import './../app/app.css';
Amplify.configure(outputs);
const client = generateClient<Schema>();
export default function App() {
function deleteTodo(id: string) {
client.models.Todo.delete({ id });
}
const [todos, setTodos] = useState<Array<Schema['Todo']['type']>>([]);
const listTodos = () => {
client.models.Todo.observeQuery().subscribe({
next: (data) => setTodos([...data.items]),
});
}
useEffect(() => {
listTodos();
}, []);
const createTodo = () => {
client.models.Todo.create({
content: window.prompt('Todo content'),
});
}
return (
<main>
<h1>My todos</h1>
<button onClick={createTodo}>+ new</button>
<ul>
{todos.map((todo) => (
<li onClick={() => deleteTodo(todo.id)} key={todo.id}>
{todo.content}
</li>
))}
</ul>
<div>
🥳 App successfully hosted. Try creating a new todo.
<br />
<a href='https://docs.amplify.aws/nextjs/start/quickstart/nextjs-app-router-client-components/'>
Review next steps of this tutorial.
</a>
</div>
</main>
);
}
ローカルの開発サーバーを起動して、リストを選択して削除できるか試してください。
npm run dev
ログインUIの実装
チュートリアルには、amplify/auth/resource.ts
ファイルに事前に定義された認証バックエンドがすでにあります。
まず、Amplify UIコンポーネントライブラリをインストールしてください。
npm add @aws-amplify/ui-react
app/page.tsx
の内容を次のように変更することで、認証機能を追加することができます。
'use client';
import type { Schema } from '@/amplify/data/resource';
import outputs from '@/amplify_outputs.json';
import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import { Amplify } from 'aws-amplify';
import { generateClient } from 'aws-amplify/data';
import { useEffect, useState } from 'react';
import './../app/app.css';
Amplify.configure(outputs);
const client = generateClient<Schema>();
export default function App() {
function deleteTodo(id: string) {
client.models.Todo.delete({ id });
}
const [todos, setTodos] = useState<Array<Schema['Todo']['type']>>([]);
function listTodos() {
client.models.Todo.observeQuery().subscribe({
next: (data) => setTodos([...data.items]),
});
}
useEffect(() => {
listTodos();
}, []);
function createTodo() {
client.models.Todo.create({
content: window.prompt('Todo content'),
});
}
return (
<Authenticator>
{({ signOut, user }) => (
<main>
<h1>My todos</h1>
<button onClick={createTodo}>+ new</button>
<ul>
{todos.map((todo) => (
<li onClick={() => deleteTodo(todo.id)} key={todo.id}>
{todo.content}
</li>
))}
</ul>
<div>
🥳 App successfully hosted. Try creating a new todo.
<br />
<a href='https://docs.amplify.aws/nextjs/start/quickstart/nextjs-app-router-client-components/'>
Review next steps of this tutorial.
</a>
</div>
<button onClick={signOut}>Sign out</button>
</main>
)}
</Authenticator>
);
}
ローカルの開発サーバーを再起動してください。
npm run dev
ログイン画面が表示されると思います。
サインアップ機能にメールドメイン制限を加える
変更をAmplifyプロジェクトに反映させる
変更反映前はドメインをブラウザから確認するとクリックしても削除されませんし、ログイン画面も表示されないことを確認してください。
コミットとプッシュの実行だけで、先ほど追加した削除機能とログイン画面の変更がデプロイしているAmplifyプロジェクトに反映されます。
git commit -am "added authenticator"
git push
バックエンドが自動的にビルド、テスト、デプロイされます。
デプロイが終わったら変更が反映されているか確認してください。
ログの確認
ビルドおよびデプロイプロセスのログは下記の赤枠の箇所を押すと確認できます。
下記はデプロイが失敗した場合の例ですが、ビルドおよびデプロイ中でもログは確認できます。
ローカルでAWS認証情報を設定する
バックエンドの更新を行うには、ローカルマシンからバックエンドの更新をデプロイするためのAWS認証情報が必要になります。
ローカルマシン上に認証情報を含むAWSプロファイルがすでにあり、AWSプロファイルにアクセス許可ポリシー(AmplifyBackendDeployFullAccess
)がある場合は、サンドボックスの作成に進んでください。
それ以外の場合は、下記を参考にAWS認証情報を設定してください。
Sandbox環境の作成
Gen2で追加されたサンドボックス環境を作成して試してみます。
npx ampx sandbox
特定の設定プロファイルを使ってサンドボックス環境を作成する場合は、次のコマンドを使用してください。
npx ampx sandbox --profile <連携しているIAMユーザーのプロファイル名>
プロファイルは、cat ~/.aws/credentials
で確認することができます。
[プロファイル名]
aws_access_key_id = YOUR_ACCESS_KEY_ID_HERE
aws_secret_access_key = YOUR_SECRET_ACCESS_KEY_HERE
サンドボックスが正常にデプロイされると、次のように表示されます。
✅ amplify-<app-name>-<$(whoami)>-sandbox-<識別子>
✨ Deployment time: XXX.XXs
Outputs:
...
Stack ARN:
...
✨ Total time: XXX.XXs
[Sandbox] Running successfulDeployment event handlers
[Sandbox] Watching for file changes...
サンドボックスを作成すると、amplify-<app-name>-<$(whoami)>-sandbox-<識別子>
というサンドボックス名で環境が作成されます。
自動的にサンドボックス環境に反映されるか確認する
ローカルでの開発環境(npm run dev
を実行している環境)でバックエンドの変更(例えば認証機能の追加など)をコードの変更が自動的に検知され、amplify push
コマンドを実行することなく、サンドボックス内で自動的にサンドボックス環境に反映されるか確認します。
まず、Sandbox環境の作成したターミナルとは別のターミナルで、ローカル環境を起動してください。
ローカル環境を既に起動している場合は再起動してください。
Sandbox環境を立ち上げたまま、データベースの内容を変更して確認してみましょう。
データベース(DynamoDB)
その後、amplify/data/resource.ts
の内容を下記に変更してください。
コメントアウトしている文章は長いので今回は省略しています。
import { a, defineData, type ClientSchema } from '@aws-amplify/backend';
const schema = a.schema({
Todo: a
.model({
content: a.string(),
})
.authorization((allow) => [allow.owner()]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'userPool',
},
});
変更前
import { a, defineData, type ClientSchema } from '@aws-amplify/backend';
const schema = a.schema({
Todo: a
.model({
content: a.string(),
})
.authorization((allow) => [allow.publicApiKey()]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'apiKey',
apiKeyAuthorizationMode: {
expiresInDays: 30,
},
},
});
元の記述では、Todoモデルの認可がpublicApiKey
(APIキー)を使用して公開され、デフォルトの認証モードが30日間有効なapiKey
であるのに対し、変更後の記述ではTodoモデルの認可がowner
(所有者)に限定され、デフォルトの認証モードがAWS Cognitoユーザープールに変更されています。
すなわち、任意の認証されたユーザーが全てのTodoレコードにアクセスできる設定から、ユーザープールを通じて認証されたユーザーのみが、自分が作成したTodoアイテムにアクセスできるように変更されています。
スキーマ定義
a.schema
を使ってモデル(GraphQLスキーマ)を定義しています。
モデル(テーブル)
モデルは、DynamoDBのテーブルとしてデプロイされます。
今回の場合はTodoテーブルがDynamoDBのテーブルとして作成されます。
フィールド(属性)
content: 文字列型 (a.string())のフィールドです。
a.string().required()
のように記述すると、必須項目になります。
認可 (authorization)
authorization((allow) => [allow.owner()])
は、レコードの作成者のみがアクセスできることを示しています。
authorization((allow) => allow.authenticated())
の場合は、認証されたすべてのユーザーがアクセスを許可されることを示しています。
スキーマタイプの定義
スキーマの型定義を行い、TypeScriptの型チェックを強化しています。
export type Schema = ClientSchema<typeof schema>;
データ定義 (defineData)
定義されたschema
とここで記述した認証モードなどのバックエンドの設定基づいて、GraphQLスキーマが生成され、AppSyncのGraphQL APIに反映されます。そのAPIを使用してデータ操作を行うことができます。
認証モード (authorizationModes)
APIキー、ユーザープールなどを使用してリソースへのアクセスを制御することができます。
変更が自動的にサンドボックス環境に反映されると、下記のように自動で変更されているのがわかると思います。
✅ amplify-<app-name>-<$(whoami)>-sandbox-<識別子>
✨ Deployment time: XXX.XXs
Outputs:
...
Stack ARN:
...
✨ Total time: XXX.XXs
[Sandbox] Watching for file changes...
File written: amplify_outputs.json
続けて、ログインしているユーザーがわかりやすいようにapp/page.tsx
の内容を次のように変更して、ログインしているユーザー名を表示するようにします。
'use client';
import type { Schema } from '@/amplify/data/resource';
import outputs from '@/amplify_outputs.json';
import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import { Amplify } from 'aws-amplify';
import { generateClient } from 'aws-amplify/data';
import { useEffect, useState } from 'react';
import './../app/app.css';
Amplify.configure(outputs);
const client = generateClient<Schema>();
export default function App() {
function deleteTodo(id: string) {
client.models.Todo.delete({ id });
}
const [todos, setTodos] = useState<Array<Schema['Todo']['type']>>([]);
function listTodos() {
client.models.Todo.observeQuery().subscribe({
next: (data) => setTodos([...data.items]),
});
}
useEffect(() => {
listTodos();
}, []);
function createTodo() {
client.models.Todo.create({
content: window.prompt('Todo content'),
});
}
return (
<Authenticator>
{({ signOut, user }) => (
<main>
<h1>{user?.signInDetails?.loginId}'s todos</h1>
<h1>My todos</h1>
<button onClick={createTodo}>+ new</button>
<ul>
{todos.map((todo) => (
<li onClick={() => deleteTodo(todo.id)} key={todo.id}>
{todo.content}
</li>
))}
</ul>
<div>
🥳 App successfully hosted. Try creating a new todo.
<br />
<a href='https://docs.amplify.aws/nextjs/start/quickstart/nextjs-app-router-client-components/'>
Review next steps of this tutorial.
</a>
</div>
<button onClick={signOut}>Sign out</button>
</main>
)}
</Authenticator>
);
}
generateClient<Schema>()
データモデルに基づいたクライアントを生成しています。これにより、定義されたスキーマに従ってDynamoDBとの連携が行われます。
client.models.Todo.observeQuery()
DynamoDBからToDo
アイテムのリストをリアルタイムで取得しています。
サンドボックスを作成する前に既にログインしていた場合は、本番用バックエンドではなくサンドボックスを使用するようになるため、新しいユーザーを再度サインアップ(新規登録)する必要があります。
2名新規登録して、それぞれのアカウントでログインした後に作成したTodoしか確認できなくなっていることを確認してください。
確認ができたらコミットとプッシュを実行することで、デプロイしているAmplifyプロジェクトに反映されます。
git commit -am "added per-user data isolation"
git push
データ > データマネージャーが作成されていることを確認してください。
(DynamoDBでテーブル、AppSyncでAPIが作成されています。)
AWS AppSyncとは
AWSサービス(DynamoDB、RDS、Lambdaなど)と連携し、GraphQLを使用してこれらのデータソースにアクセスするAPIを作成します。
リアルタイムなデータクエリやデータ同期を容易に実現できるサービスで、アプリケーションのデータの取得や更新をリアルタイムで処理し、クライアントとバックエンドの間でデータを同期する機能を提供しています。
サンドボックスの削除
コンソールからも削除できますが、サンドボックスを起動しているターミナルでCtrl
+C
を実行するとSandboxを削除することができます。
AWS Amplifyのサンドボックス環境に関連するリソースを削除して良いか確認されますので、削除したい場合はy
を入力してください。
? Would you like to delete all the resources in your
sandbox environment (This cannot be undone)? (y/N)
CLIで削除することも可能です。
npx ampx sandbox delete
オプション
-
--name
: 異なるサンドボックス環境を区別するためのオプションの名前。デフォルトは、package.json 内の名前です。 -
--profile
: AWS プロファイル名。 -
-y
,--yes
: サンドボックス環境を削除する前に確認を求めません。
Generate forms
データモデルに基づいて自動的にフォームを生成するツールです。
フォームのタイプは下記の2種類があります。
作成フォーム
新しいデータレコードを作成するためのフォームです。空の入力フィールドが提供され、ユーザーが新しいデータを入力できるようになっています。
更新フォーム
既存のデータレコードを更新するためのフォームです。特定のレコードのデータをプリロードし、更新が可能です。このフォームはidや直接モデルを受け取るプロップを通じて特定のデータにバインドされます。
サンドボックス環境のデプロイメントが完了していることを確認してから、実行してください。
また、ui-components
フォルダーが既に存在する場合は、フォームの再生成によって現在のフォームファイルが上書きされたり、変更されたりする可能性があるためバックアップを取ってください。
npx ampx generate forms
ルートディレクトリにui-components
というディレクトリが作成されます。
下記のように記述することで、データレコードの作成と更新ができるようになっています。
コードを見ていただければわかるかと思いますが、これでCRUD操作は全てできるようになっています。
'use client';
import type { Schema } from '@/amplify/data/resource';
import outputs from '@/amplify_outputs.json';
import { default as TodoCreateForm } from '@/ui-components/TodoCreateForm';
import TodoUpdateForm from '@/ui-components/TodoUpdateForm';
import { Authenticator, ThemeProvider } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import { Amplify } from 'aws-amplify';
import { generateClient } from 'aws-amplify/data';
import { useEffect, useState } from 'react';
import './../app/app.css';
Amplify.configure(outputs);
const client = generateClient<Schema>();
export default function App() {
function deleteTodo(id: string) {
client.models.Todo.delete({ id });
}
const [todos, setTodos] = useState<Array<Schema['Todo']['type']>>([]);
const [updateId, setUpdateId] = useState<string>('');
console.log('updateId', updateId);
const listTodos = () => {
client.models.Todo.observeQuery().subscribe({
next: (data) => setTodos([...data.items]),
});
};
useEffect(() => {
listTodos();
}, []);
const createTodo = () => {
client.models.Todo.create({
content: window.prompt('Todo content'),
});
};
return (
<ThemeProvider>
<Authenticator>
{({ signOut, user }) => (
<main>
<h1>{user?.signInDetails?.loginId}'s todos</h1>
<h1>My todos</h1>
<button onClick={createTodo}>+ new</button>
<ul>
{todos.map((todo) => (
<li
onClick={() => {
// deleteTodo(todo.id);
setUpdateId(todo.id);
}}
key={todo.id}
>
{todo.content}
</li>
))}
</ul>
<div>
🥳 App successfully hosted. Try creating a new todo.
<br />
<a href='https://docs.amplify.aws/nextjs/start/quickstart/nextjs-app-router-client-components/'>
Review next steps of this tutorial.
</a>
</div>
<button onClick={signOut}>Sign out</button>
<p>新しいTodoを作成する場合</p>
<TodoCreateForm />
<p>
Todoを更新する場合
<br />
先に編集したいTodoを選択してください。
</p>
<TodoUpdateForm id={updateId} />
</main>
)}
</Authenticator>
</ThemeProvider>
);
}
ストレージの追加 (S3)
amplify/storage
という名前の新しいフォルダを作成し、resource.ts
ファイルを追加します。
cd amplify && mkdir storage && cd storage && touch resource.ts
import { defineStorage } from '@aws-amplify/backend';
export const storage = defineStorage({
name: 'amplify-template'
});
defineBackend
関数の中にstorage
を追加します。
これにより、プロジェクト内で定義したストレージリソース(S3)が作成され、使用することができます。
import { storage } from './storage/resource';
defineBackend({
...
storage
});
amplifytemplate
という文字が含まれたS3が作成されているはずです。
関数の追加 (Lambda)
amplify
フォルダに移動し、my-first-function
という任意の関数名のフォルダを作成し、その中にresource.ts
ファイルを作成し、
cd amplify && mkdir my-first-function && cd my-first-function && touch resource.ts
次のコードを追加してください。
import { defineFunction } from "@aws-amplify/backend";
export const myFirstFunction = defineFunction({
name: "my-first-function",
entry: "./handler.ts"
});
defineFunction
関数定義を行う関数です。
name
関数の名前を指定します。
entry
Lambda関数のエントリーポイントを指定します。
my-first-function/handler.ts
ファイルを作成し、
touch handler.ts
次のコードを追加してください。
event
に型を定義していないとデプロイ時にエラーになるので、今回はAPI Gatewayをトリガーとする場合の型を定義しています。
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
export const handler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
return {
statusCode: 200,
body: 'Hello from my first function!',
};
};
defineBackend
関数の中にmyFirstFunction
を追加します。
これにより、プロジェクト内で定義したカスタムLambda関数が作成され、使用できるようになります。
import { myFirstFunction } from './my-first-function/resource';
defineBackend({
...
myFirstFunction,
});
myfirstfunction
という文字が含まれたLambda関数が作成されているはずです。
サンドボックスの秘密情報の管理
秘密情報を登録できます。
npx ampx sandbox secret set <キー名>
値を入力してください。
? Enter secret value [input is masked]
登録できているか確認するために秘密情報を表示して確認してください。
npx ampx sandbox secret list
秘密情報の削除
npx ampx sandbox secret remove <キー名>
カスタムドメイン
CLI
FAQ
ポリシーの追加
アプリケーションのデプロイプロセスの一環として、AmplifyはAppSyncで新しいスキーマを作成しようとした際に、指定されたIAMユーザーが該当の操作を実行する権限を持っていなかったため、デプロイが失敗しました。
IAMユーザーの許可ポリシーに下記を追加しました。
AdministratorAccess-Amplify
にAWSAppSyncSchemaAuthor
を追加した。
終わりに
何かありましたらお気軽にコメント等いただけると助かります。
ここまでお読みいただきありがとうございます🎉
Discussion