DEV Community

hirooka kazuya
hirooka kazuya

Posted on

review of preparing dev project of aws amplify gen2 and next.js sandbox

in these days, i prepare the developing envioromnent with aws amplify gen2 and next.js. this is the summary.

install AWS CLI on [https://awscli.amazonaws.com/AWSCLIV2.msi]

Set up authentication credentials

aws configure
Enter fullscreen mode Exit fullscreen mode
AWS Access Key ID: あなたのアクセスキー
AWS Secret Access Key: あなたのシークレットキ
Default region name: 例 ap-northeast-1 (東京)
Default output format: json (Enter でOK)
Enter fullscreen mode Exit fullscreen mode

confirm AWS CLI

aws --version
Enter fullscreen mode Exit fullscreen mode

Install node.js on https://nodejs.org/ja/download

Move to current directory

cd (current directory)
Enter fullscreen mode Exit fullscreen mode

Recreate npm Project / Project Setup

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

Install Amplify Backend and Frontend SDKs in the Next.js Project Root Directory

npm install @aws-amplify/backend
Enter fullscreen mode Exit fullscreen mode
npm install @aws-amplify/data-schema
Enter fullscreen mode Exit fullscreen mode
npm install aws-amplify
Enter fullscreen mode Exit fullscreen mode
npm install @aws-amplify/ui-react
Enter fullscreen mode Exit fullscreen mode

Create Directories and Files

mkdir amplify
Enter fullscreen mode Exit fullscreen mode
mkdir amplify\data
Enter fullscreen mode Exit fullscreen mode
mkdir amplify\auth
Enter fullscreen mode Exit fullscreen mode
type nul > amplify\backend.ts
Enter fullscreen mode Exit fullscreen mode
type nul > amplify\data\resource.ts
Enter fullscreen mode Exit fullscreen mode
type nul > amplify\auth\resource.ts
Enter fullscreen mode Exit fullscreen mode

Open the created amplify/backend.ts file and add the following content:

// amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
// 認証リソースをインポート
// aws公式サイトを見ても.tsがついていないが、npx ampx sandobxのときにtsがついていないとエラーが出る
import { auth } from './auth/resource.ts'; 
import { data } from './data/resource.ts'; 

// The Amplify backend definition.
defineBackend({
  auth, // ここにインポートした 'auth' を追加
  data, // ここにインポートした 'data' を追加
});
Enter fullscreen mode Exit fullscreen mode

Open the created amplify/data/resource.ts file and add the following content:

// @aws-amplify/backend から必要なモジュールをインポートします。
// a: スキーマ定義のためのヘルパー (例: a.model, a.string)
// defineData: データリソース全体を定義するための関数
// type ClientSchema: フロントエンドでの型付けに使用されるユーティリティ型

import { a, defineData, type ClientSchema } from '@aws-amplify/backend';

// データのスキーマを定義します。これはGraphQLスキーマの定義に相当します。
// a.modelと定義するだけで、query(取得)、mutation(更新)も自動で定義。
const schema = a.schema({
  // 'Todo' という名前のデータモデルを定義します。
  // これは AWS DynamoDB のテーブルに対応します。
  Todo: a.model({
    // 'content' というフィールドを定義します。型は文字列 (String) です。


    content: a.string(),
    // 'isDone' というフィールドを定義します。型は真偽値 (Boolean) です。
    isDone: a.boolean()
  })
    // このモデルに対するアクセス許可(認可ルール)を定義します。
    // ここでは、パブリックな API キー (publicApiKey) を持つユーザー(クライアント)にアクセスを許可しています。
    .authorization(allow => [allow.publicApiKey()])
});

// Used for code completion / highlighting when making requests from frontend
// フロントエンド (Amplify クライアント) でリクエストを行う際、
// スキーマの型補完やハイライト表示に使用するためにエクスポートされる型です。
export type Schema = ClientSchema<typeof schema>;

// defines the data resource to be deployed
// デプロイされるデータリソース(AppSync APIとDynamoDBテーブルなど)を定義し、設定します。
export const data = defineData({
  // 上で定義した GraphQL スキーマを設定します。
  schema,
  // 認証モードを設定します。
  authorizationModes: {
    // デフォルトの認証モードとして 'apiKey' を設定します。
    defaultAuthorizationMode: 'apiKey',
    // API Key 認証モードの詳細設定です。
    apiKeyAuthorizationMode: {
      // API キーの有効期限を30日に設定します。
      expiresInDays: 30
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Open the created amplify/auth/resource.ts file and add the following content:

// amplify/auth/resource.ts
import { defineAuth, secret } from "@aws-amplify/backend";

/**
 * The Amplify Auth resource definition.
 */
export const auth = defineAuth({
  loginWith: {
    // ユーザー名とパスワードによるログインを有効化
    email: true,
  },
});
Enter fullscreen mode Exit fullscreen mode

Create Directories and Files

type nul > app\providers.tsx
Enter fullscreen mode Exit fullscreen mode
type nul > app\useAmplifyClient.ts
Enter fullscreen mode Exit fullscreen mode
type nul > components\TodoList.tsx
Enter fullscreen mode Exit fullscreen mode

Open the app/layout.tsx file and add the following content:

// app/layout.tsx

// ★ "use client" はここに書かない(Server Componentのままにする)

import './globals.css' // グローバルCSSのインポート

// 新しく作成した Client Component ラッパーをインポート
import Providers from '../app/providers' 

//ここは、ログイン前の画面のroot

export default function RootLayout({
  children,
}: {
  children: React.ReactNode // ★ childrenを受け取る形式が必須
}) {
  return (
    <html lang="ja">
      <body>
        {/* Providers (Client Component) でラップ */}
        <Providers>{children}</Providers> 
      </body>
    </html>
  )
}
Enter fullscreen mode Exit fullscreen mode

Open the app/page.tsx file and add the following content:

// app/dashboard/page.tsx

'use client'
import { Authenticator } from '@aws-amplify/ui-react';
import TodoList from '../components/TodoList';// TodoListコンポーネントを切り出す

export default function DashboardPage() {
  return (
    <Authenticator>
      {({ signOut, user }) => (
        <main>
          <h1>Hello, {user?.username}!</h1>
          <TodoList /> {/* 👈 Todoリストコンポーネントを配置 */}
          <button onClick={signOut}>サインアウト</button>
        </main>
      )}
    </Authenticator>
  );
}
Enter fullscreen mode Exit fullscreen mode

Open the app/providers.tsx file and add the following content:

// app/providers.tsx

'use client' // ★ これをファイルの先頭に記述!

import '@aws-amplify/ui-react/styles.css'
import { Amplify } from 'aws-amplify'
import outputs from '../amplify_outputs.json'


//ここで、Providersコンポーネントを定義している
//React.ReactNodeとは何だろうか、上記のimoprtのどれかに要素が入っているのだろうが

// ここで設定を一回だけ実行する
// SSR環境での二重実行を防ぐための簡易チェック

    Amplify.configure(outputs)


export default function Providers({ children }: { children: React.ReactNode }) {
  // ここでContext Providerなどを提供してもOK
  return <>{children}</>
}
Enter fullscreen mode Exit fullscreen mode

Open the app/useAmplifyClient.ts file and add the following content:

// src/hooks/useAmplifyClient.ts (例)
'use client'

import { useState, useEffect } from 'react';
import { generateClient, Client } from 'aws-amplify/data';
import type { Schema } from '../amplify/data/resource'; // 👈 パスに注意

// クライアントを生成し、その状態を返すカスタムフック
export function useAmplifyClient() {
  // clientの状態を管理し、初期値はnullにする
  const [client, setClient] = useState<Client<Schema> | null>(null);

  // コンポーネントがマウントされたら、クライアントを生成する
  useEffect(() => {
    // Amplifyの設定が Providers.tsx で完了していることを期待
    setClient(generateClient<Schema>());
  }, []);

  return client; // client (または null) を返す
}
Enter fullscreen mode Exit fullscreen mode

Open the components/TodoList.tsx file and add the following content:

// app/dashboard/TodoList.tsx (または page.tsx)

'use client'

import { useState, useEffect } from "react";
// スキーマはフック内で使用されるため、ここでは不要(フックが内部でインポートしている)
// import type { Schema } from "../../amplify/data/resource"; 
// import { generateClient, Client } from "aws-amplify/data"; 

// 🚨 【重要】Todoリストの処理に必要なSchema型だけはインポートし直す
import type { Schema } from "@/amplify/data/resource"; 
import { useAmplifyClient } from '@/app/useAmplifyClient'; // 👈 カスタムフックをインポート

export default function TodoList() {
    // 🎉 クライアントの初期化ロジックをフックに任せる
    // clientにはバックエンドの全情報が入っている
    // しかし、clientは読み込み時にnull ->値に変わるが、
    // createのタイミングなどそれ以外では不変
    const client = useAmplifyClient();

    // todosがメモの配列、それをsetTodos関数で更新する
    // ()内は初期値で、今回は空配列[]
    // <>内はtypescriptの型定義

    // Schemaはamplify/data/resource.tsで定義したもの
    // プロパティでTodo,typeをたどっている
    // ただし、あくまでtodosのリストなので最後に[]をつけている
    // でも結局は配列のどの要素も型は同じ
    // 型にも引数にも[]をつけて単一の値と区別する
    const [todos, setTodos] = useState<Schema["Todo"]["type"][]>([]);

    // 以降、ロジックはシンプルになる

    // データ取得関数、データベースからデータを読み込む
    const fetchTodos = async () => {
        if (!client) return;

        // await client.models.Todo.list()で返されるオブジェクトのイメージ
        // {
        //     data: [ // 実際のTodoの配列
        //         { id: "1", status: "牛乳を買う", isDone: false },
        //         { id: "2", status: "メールをチェック", isDone: true }
        //     ],
        //     // その他の情報(トークンなど、データ取得のメタ情報)
        //     nextToken: null 
        // }       

        // 分割代入 上記のうち、dataプロパティだけを取得する
        // だからamplify/data/resource.tsで正しく定義する必要あり

        const { data: items } = await client.models.Todo.list();

        // 以下でitemsを使った状態の更新が行われ、
        // 状態が更新されると、UIの更新が行われる(reactの基本機能)
        setTodos(items);
    };

    // 画面読み込み時クライアントがセットされた後、データをフェッチする
    // clientが読み込み後にnull ->値に変化することに対応している
    // useEffectはUIと関係ない処理の実行に使う
    // 二個引数を取っている
    // 一つ目は実行したい処理
    // 二つ目がトリガー
    // これは、読み込み時のclientがnull->値に変わった時の動作
    // それ以降はclientはcreateの際も不変
    useEffect(() => {
        if (client) {
            fetchTodos();
        }
    }, [client]); 

    // データ作成関数
    const createTodo = async () => {

        if (!client) return;

        // client.models.Todo.createでデータベースにhttpリクエスト
        // window.pronpt()はブラウザの標準機能の利用
        await client.models.Todo.create({
            content: window.prompt("Todo content?"),
            isDone: false,
        });
        fetchTodos();
    }

    // クライアントが生成されるまでローディング表示をする
    if (!client) {
        return <div>データを読み込み中...</div>;
    }

    // {...}:JSX内でJavascriptの式を埋め込み
    // key=idはUIには現れないが必須
    return (
        <div>

            <button onClick={createTodo}>Add new todo</button>
            <ul>
                {todos.map(({ id, content }) => (
                    <li key={id}>{content}</li>
                ))}
            </ul>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

execute the sandbox
npx ampx sandbox

when finish sandbox, Ctrl+C, and then npx ampx sandbox delete

to conform hot reload of sandbox, check console of AWS AppSync Schema.

Top comments (0)