Amplify Gen2+Next.js+devContainer+認証+データとか諸々試してみた
はじめに
最近AmplifyのGen2が出て、開発体験が良くなったらしいので試してみた内容をまとめた記事です。
この記事で書いてあることは以下のとおりです。
- devContainerでNext.js+amplifyの開発環境
- GitHub連携でAmplifyHostingへのデプロイ
- 本番環境、検証環境、ローカル環境それぞれ開発手順とデプロイ確認
- amplify(DynamoDB)とNext.jsを使ったCRUD実装
- amplify(Cognito)とNext.jsを使ったログイン機能実装
- サイトにBasic認証を追加
実装したリポジトリはこちらに置いておきます。
リポジトリを作成してdevContainerで環境構築
まず、GitHubでリポジトリを作成してclone。
Next.js + TypeScript + App Routerの環境を構築
npx create-next-app@latest .
.devcontainer
フォルダを作成。
.devcontainer
フォルダ内にDockerfile
ファイルを作成。
下記を記載。
FROM node:20-alpine
# Install dependencies
RUN apk update && apk add curl git openssh bash
# Install aws cli
RUN apk add --no-cache aws-cli
.devcontainer
フォルダの中にdevcontainer.json
ファイルを作成。
以下を記載。extensionsはお好みで。
{
"name": "Existing Dockerfile",
"build": {
"dockerfile": "Dockerfile"
},
"forwardPorts": [3000],
"customizations": {
"vscode": {
"settings": {
"editor.formatOnSave": true
},
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"mhutchie.git-graph",
"PKief.material-icon-theme",
"eamodio.gitlens"
]
}
}
}
vscodeの左下の青いアイコンをクリックして、
「Reopne in Container」をクリック
左下の青いエリアがこんな感じになればOK。
vscodeのターミナルで下記を実行。
npm run dev
ローカルホストでNext.jsの初期画面が表示される。
次に、GitHubでコードを管理するためにgitのssh設定をする
~/.sshで実行コマンドを実行
ssh-keygen -t rsa
~/.sshでconfigファイルを作成して下記を記載
Host github github.com
HostName github.com
IdentityFile ~/.ssh/id_rsa
User git
GitHubのSSHのキー設定をする
下記コマンドで疎通確認
ssh -T github.com
変更コードを全てプッシュする。
環境構築done.
AmplifyHostingでデプロイ
AWSのAmplifyにアクセスして「新しいアプリを作成」をする
GitHubのアクセス許可を設定
対象のブランチを選択する
ブランチはmainもしくはmaster
アプリケーションの設定は全部初期設定でOK。
確認画面が表示されるので「保存してデプロイ」する
デプロイ済みになったら「ドメイン」のリンクをクリックして確認すると、next.jsの初期ページが表示されている。
開発ブランチ作成とデプロイ
開発環境用に新しくdevelopブランチを作成してそのままプッシュ。
AWSのAmpifyの左にある「アプリケーションの設定」>「ブランチ設定」にアクセス。
「ブランチの追加」ボタンを押す
接続するブランチに先ほどプッシュしたdevelopを選択して「ブランチを追加」する
Ampifyの概要を見ると、設定したブランチのソースコードでデプロイが走る。
開発環境のdevelopにコードをマージしてデプロイを確認するために、featureブランチを作って編集したコードをプッシュしてしてdevelopにマージしてみる。
feature/hoge
ブランチを作成して、page.tsx
のコードを編集。
mainタグの中身をhello world.にだけにしてみる。
<main className="flex min-h-screen flex-col items-center justify-between p-24">
Hello World.
</main>
プッシュしてPR(プルリクエスト)を作成してdevelopにマージする。
マージするとAmplifyのdevelopブランチが自動でビルドしてデプロイしてくれる。
デプロイが完了したらdevelopのドメインをクリックして変更を確認。
Hello World.になってる。
ここでmainブランチのドメインもクリックして確認してみる。
またnext.js初期設定のページになっている。
GitHubでdevelopをmainにマージするPRを作成してマージ。
Amplifyのmainブランチのデプロイが走る。
デプロイが完了したらmainブランチのドメインをクリックして確認してみると、developの内容が反映されてデプロイが完了している。
Amplify Gen2を使う環境を整える
公式ドキュメントにあるローカル環境を整える手順を進める。
1. Create user with Amplify permissions(Amplify権限を持つユーザーを作成する)
書かれてる通りに進める。
2. Create password for user(ユーザーのパスワードを作成する)
書かれてる通りに進める。
3. Install the AWS CLI(AWS CLIをインストールする)
この手順はスキップしてください。
DockerfileでAWS CLIをインストールする設定を書いているので、既に開発環境にインストールされている状態です。
4. Set up local AWS profile(ローカルAWSプロファイルを設定する)
ドキュメントに書かれているaws configure sso
を実行すると、色々入力を求められますが、手順1の最後に表示された情報を元に入力を進めていきます。
2つ目に聞かれるSSO region:
は、ドキュメントには<YOUR START SESSION URL>
とありますが、regionを聞かれているのでregionの情報を入力すれば大丈夫です。
無事入力を進めていくとブラウザでログインを求められるのでログインをします。
ログイン後にも設定の入力が求められますが、私の場合下記情報は既に入力?されている状態になっていました。
The only AWS account available to you is:
Using the account ID
The only role available to you is:
Using the role name
なので、続きのCLI default client Region [None]:
から設定の続きを入力します。
aws s3 ls --profile default
が表示されたら設定完了です。
Amplifyの環境構築
Quickstartでは既に環境が整っているのですが、実際に導入しようとするとおそらく手動で設定する必要が出てくるので試してみます。
下記コマンドを実行。
npm create amplify@latest
実行が完了するとamplify
フォルダが作成されて中にいろんなファイルが作られている。
下記コマンドを実行してサンドボックス環境を作成する。
npx ampx sandbox
サンドボックス環境が作成されると、プロジェクトのルートにamplify_outputs.json
ファイルが作成されます。
サンドボックス環境が作成されると常に監視状態になり、amplifyフォルダの中を編集すると自動でサンドボックス環境が更新されます。
この時点で認証(Cognito)とデータベース(DynamoDB)の作成ができてる状態です。
サンドボックスの情報はAWSのCloudFormation
を見ると色々書いてあります。
例えば、CloudFormationの出力タブを見てみると、userPoolId
、awsAppsyncApiId
の値が設定されています。
AWSのCognito
を開いて、userPoolId
に書かれている値を探してみるとその値でユーザープールが作成されているのがわかります。
また、AWSのDynamoDB
を開いて「項目を検索」を見てみると、awsAppsyncApiId
に書かれている値でTodoのテーブルが作成されているのがわかります。
このTodoのテーブルはamplifyフォルダにあるamplify/data/resource.ts
に書かれている定義で作成されています。
ログイン機能やデータのCRUD機能を実装すると、それぞれこのsandboxに書かれている場所に登録されます。
CRUDを実装
Next.jsからCRUD操作する実装をします。
基本的には公式ドキュメントの下記URLを元に進めていきます。
まず、今の段階ではauthの設定はしないので、一旦amplify/backend.ts
のauthをコメントアウトする。
import { defineBackend } from "@aws-amplify/backend";
import { auth } from "./auth/resource";
import { data } from "./data/resource";
defineBackend({
// auth,
data,
});
amplify/data/resource.ts
の下記のように書き換える
import { type ClientSchema, a, defineData } 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 },
},
});
page.tsx
を下記コードに変更。
"use client";
import { generateClient } from "aws-amplify/data";
import type { Schema } from "@/amplify/data/resource";
import { Amplify } from "aws-amplify";
import outputs from "@/amplify_outputs.json";
import { useEffect, useState } from "react";
Amplify.configure(outputs);
const client = generateClient<Schema>();
type TodoFormProps = {
todo: Schema["Todo"]["type"];
handleUpdate: (string: string) => void;
handleDelete: () => void;
};
const TodoForm = ({ todo, handleUpdate, handleDelete }: TodoFormProps) => {
const [input, setInput] = useState<string>("");
useEffect(() => {
setInput(todo.content || "");
}, []);
return (
<div className="flex">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<div className="flex gap-1">
<button
onClick={() => handleUpdate(input)}
className="border-[1px] border-gray-500 px-2"
>
更新
</button>
<button
onClick={handleDelete}
className="border-[1px] border-gray-500 px-2"
>
削除
</button>
</div>
</div>
);
};
export default function Home() {
const [todos, setTodos] = useState<Schema["Todo"]["type"][]>([]);
const [input, setInput] = useState<string>("");
const handleGetTodos = async () => {
const { data: todos } = await client.models.Todo.list();
setTodos(todos);
};
const handleCreate = async () => {
await client.models.Todo.create({
content: input,
});
setInput("");
await handleGetTodos();
};
const handleDelete = async (id: string) => {
await client.models.Todo.delete({
id,
});
handleGetTodos();
};
const handleUpdate = async (id: string, content: string) => {
await client.models.Todo.update({
id,
content,
});
};
useEffect(() => {
(async () => {
await handleGetTodos();
})();
}, []);
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="flex gap-1">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<button
onClick={handleCreate}
className="border-[1px] border-gray-500 px-2 "
>
登録
</button>
</div>
{todos.map((todo) => {
return (
<div key={todo.id}>
<TodoForm
todo={todo}
handleUpdate={(content) => handleUpdate(todo.id, content)}
handleDelete={() => handleDelete(todo.id)}
/>
</div>
);
})}
</main>
);
}
このままだとamplify_outputs.json
をimportする時にエラーがでるので、tsconfig.json
のpath
を下記のコードに修正。
"paths": {
"@/*": ["./*"]
}
ローカル環境で動作確認をしてみると、こんな感じになってると思います。
sandboxのDynamoDBを確認してみると、データが登録されています。
これをdevelopの環境にデプロイしたいので、バックエンドをビルドするためのamplify.yml
ファイルを作成します。
プロジェクトのルートに作成して下記の内容を記載。
version: 1
backend:
phases:
build:
commands:
- npm ci --cache .npm --prefer-offline
- npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID
frontend:
phases:
build:
commands:
- npm ci --cache .npm --prefer-offline
- npm run build
artifacts:
baseDirectory: .next
files:
- "**/*"
cache:
paths:
- .next/cache/**/*
- .npm/**/*
- node_modules/**/*
ではこれらをプッシュしてPRを作成してdevelopにマージします。
デプロイが終わったらdevelopのドメインにアクセスしてTODOを登録すると、develop用のDynamoDBにデータが登録されるようになります。
ログイン機能を実装
認証も基本的に公式ドキュメント元に実装していきます。
先ほどコメントアウトしたamplify/backend.ts
のauth部分のコメントを解除します。
import { defineBackend } from "@aws-amplify/backend";
import { auth } from "./auth/resource";
import { data } from "./data/resource";
defineBackend({
auth,
data,
});
amplifyで簡単にログインUIが作れる@aws-amplify/ui-react
をinstallする。
npm add @aws-amplify/ui-react
pate.tsx
にログインのUIを実装する。
量が多くなるので一部コードを省略しているので追加の箇所をコピペしてください。
"use client";
import { generateClient } from "aws-amplify/data";
import type { Schema } from "@/amplify/data/resource";
import { Amplify } from "aws-amplify";
import outputs from "@/amplify_outputs.json";
import { useEffect, useState } from "react";
// 追加
import { Authenticator } from "@aws-amplify/ui-react";
// 追加
import "@aws-amplify/ui-react/styles.css";
Amplify.configure(outputs);
const client = generateClient<Schema>();
...省略
export default function Home() {
...省略
return (
// 追加
<Authenticator>
{({ signOut, user }) => (
<main className="flex min-h-screen flex-col items-center justify-center p-24 gap-5">
// 追加
<h1>Hello {user?.username}</h1>
...省略
// 追加
<button
onClick={signOut}
className="w-[100px] border-[1px] border-gray-500 px-2"
>
Sign out
</button>
</main>
)}
</Authenticator>
);
}
実装するとこんなログイン画面が表示されてメールアドレスでアカウント登録、ログインができるようになります。
では、コードをプッシュしてdevelopにマージします。
デプロイが完了したらdevelopのドメインを確認すると、ローカルで開発していた時と同じ見た目に変わっていると思います。
同じようにdevelop用のアカウントを作成するとログインできるようになります。
Basic認証をつける
ホスティングメニューのアクセスコントロールから「アクセス管理」ボタンをクリック
Basic認証をつけたいブランチに「アクセス設定」を「制限」にしてユーザーネームをパスワードを設定して保存する。
保存したらサイトにアクセスしてBasic認証がついてることを確認しましょう。
おわりに(感想)
思ったより簡単にホスティング、デプロイ、バックエンド連携ができて感動しました。
バックエンドのテーブル定義もTypeScriptで型定義をしてるみたいにできるので、かなり直感的で楽だなぁと感じました。
AmplifyのイメージもGen2で結構変わりました。
Gen1はteam-provider-info.jsonの扱いがなんかややこしいイメージがありましたが、Gen2のamplify_outputs.jsonは分かりやすくなりました。
また、Gen1はいちいち環境を確認してampify pushするのがめんどくさかったのですが、Gen2ではsandbox環境を起動するだけなので煩わしさが減って開発に集中できる気がします。
他には、自分のローカルで環境を構築した時はいろんなエラーに悩まされましたが、devContainerを使って1から環境を構築することで、シンプルに環境が作れました。
あとは複数人で開発をする時にどう進めると良いかが気になりますね。
次はバックエンドをRDSと連携してみたいなと思っています。
Discussion