LoginSignup
29
21

手を動かして0から理解するAmplify Gen2

Last updated at Posted at 2024-05-19

今週は、AWS Startup Loft Osaka Pop-Upにお邪魔してきました。

コワーキングスペースを提供してくれたり、日替わりでさまざまなイベントが開催されていました。(火曜日から木曜日まで、3日間もお邪魔しました。暖かく迎えていただいて大変感謝しております)

木曜日にはAmplifyに関するイベントがあり、GAされたばかりのGen2に関して学ぶことができました。なんと AmplifyのプロダクトマネージャのMattさんからのDeep dive もあり、 裏側までちら見せ してもらうという贅沢な場でした。(現地はAM 6:00ぐらいと言ってた気がします。Mattさんありがとうございました!!)

Amplify Gen2を始めるなら今です!!

Amplifyとは

Amplifyは、「AWSを意識しないでAWSを使える」という一見不思議なサービスです。

ただ、その威力は偉大です。Amplifyを使うことで、 フロントエンド(=ユーザーへ届ける価値)に開発リソースを集中させて、バックエンドは、AWSさんがよしなにやってくれる というありがたいサービスです。

イベントのLTでもAmplifyを活用して、スピード感のある開発をされている事例の発表がありました。

Amplify Gen2とは

イベントの発表で、Amplify Gen1に対して以下のようなフィードバックがあったと紹介されていました。

  • 魔法の理解
    • Amplifyが抽象化してインチキしているような感覚が気に入っているが、抽象化されすぎていてAmplifyが未対応の機能を実装しようとすると難しくなる
  • ローカル開発の高速化
    • ローカルでの変更を素早く確認したい。また、複数開発者で干渉することなく開発を行いたい
  • より安全な本番ロールアウト
    • 本番ロールアウトを安全にしたい
    • 柔軟なデプロイオプションがほしい

これらを解決するためにGen2では以下のように改善されました。(これは私の理解です。もっと他に色々あると思います)

  • AWSリソースの構築はCDKで行う
  • SandBox機能の追加
  • Gitとの統合

とりあえず始めたい方向け

Amplify Gen2のドキュメントにクイックスタートが用意されています。とりあえず触ってみようという方はこちらから入門するのが良いと思います。

私も実際に試したところすんなり動作しました。ただ、中身の理解ができなかったので、Amplify未導入環境から導入して理解を深めました。

前置きが長くなりましたが、私が手を動かして理解を進めた際の内容を共有させていただきます。

開発環境

VSCodeのdevcontainerで以下の開発環境を整えました。

  • node: v22.1.0
  • npm: 10.7.0
  • AWS CLI: 2.15.52

AWSの認証情報はdefaultプロファイルに設定済みとして説明をすすめます。

Amplifyは大阪リージョンで使えるので、ぜひ大阪リージョン(ap-northeast-3)で使ってみてください。

Reactプロジェクトを作成

Vite(ヴィートと発音するようです)を使って、ReactとTypeScriptのプロジェクトを作成します。

npm create vite@latest gen2-app -- --template react-ts

gen2-appディレクトリが作成され、以下のファイルが生成されます。

tree
.
├── index.html
├── package.json
├── public
│   └── vite.svg
├── README.md
├── src
│   ├── App.css
│   ├── App.tsx
│   ├── assets
│   │   └── react.svg
│   ├── index.css
│   ├── main.tsx
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

4 directories, 13 files

package.jsonはこんな感じです。

package.json
package.json
{
  "name": "gen2-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.66",
    "@types/react-dom": "^18.2.22",
    "@typescript-eslint/eslint-plugin": "^7.2.0",
    "@typescript-eslint/parser": "^7.2.0",
    "@vitejs/plugin-react": "^4.2.1",
    "eslint": "^8.57.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.6",
    "typescript": "^5.2.2",
    "vite": "^5.2.0"
  }
}

まずはこの状態で画面を確認します。

cd gen2-app
npm install
npm run dev

http://localhost:5173/にアクセスするとトップページが確認できます。

プロジェクトにAmplifyを追加

Amplifyを追加します。必要なライブラリをインストールします。

npm add --save-dev @aws-amplify/backend@latest @aws-amplify/backend-cli@latest

amplify/backend.tsを作成します。

amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';

defineBackend({});

最低限これで完了です。(この先で、いくつか編集していきます)

サンドボックスの起動

Gen2の目玉機能である サンドボックス を起動してみましょう。

サンドボックス機能の詳細はこちらに記載があります。

npm run devを実行中のターミナルと別のターミナルを開き、以下のコマンドを実行します。

npx ampx sandbox

サンドボックスをはじめて使用する際には、以下のエラーが表示されます。

The given region has not been bootstrapped. Sign in to console as a Root user or Admin to complete the bootstrap process, then restart the sandbox.

この場合は、URLへアクセスを促すダイアログが表示されると思うので、指示に従ってブートストラップを行ってください。

https://ap-northeast-3.console.aws.amazon.com/amplify/create/bootstrap?region=ap-northeast-3

かっこいい!!!(裏側ではおそらくcdk bootstrapを実行しているものと思われます)

ブートストラップ完了後、再度npx ampx sandboxを実行してください。

途中、以下のような黄色や赤、緑とカラフルな文字が表示されますが、正常ですのでお待ち下さい。

以下のメッセージが表示されたらサンドボックスは作成完了です。

[Sandbox] Watching for file changes...
File written: amplify_outputs.json

Gen2ではこのターミナルを起動しっぱなしで作業をします。ローカルのファイルを更新するとそれを検知し、AWSのリソースに変更が必要な場合は随時反映されます。

自動生成されるamplify_outputs.jsonは以下のような内容です。まだAWSのリソースをひとつも作成していないため、バージョン番号のみです。

{
  "version": "1"
}

AWSが提供しているテンプレートの.gitignoreには以下の記述があるので、以下のファイルはバージョン管理不要です

.gitignore
# amplify
.amplify
amplify_outputs*
amplifyconfiguration*

認証機能(Cognito)を追加

バックエンド側

それではAWSのリソースを追加しましょう。
まずは、Cognitoによる認証を追加します。

amplify/auth/resource.tsを作成します。

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

export const auth = defineAuth({
    loginWith: {
        email: true
    }
});

次にamplify/backend.tsauthを追記します。

amplify/backend.ts
  import { defineBackend } from '@aws-amplify/backend';
+ import { auth } from './auth/resource';

- defineBackend({});
+ defineBackend({
+     auth,
+ });

amplify/backend.tsを保存すると、バックエンド(=AWS)にCognitoが必要と判断され、自動でデプロイが行われます。

言葉では伝わりづらいかもと思い、動画にしました。

CloudFormationを確認すると、Cognitoが作成されていることがわかります。

このように、 「AWSのリソースを変えたからデプロイして確認」 ではなく、 「コードを変更したら即座に反映」 という動きをするのがAmplify Gen2の特徴です。

フロントエンド側のソースを修正して保存したら即座に反映されますよね。それと同等のことがAWSのリソースでもできる感覚です!!

この時点のamplify_outputs.json

Cognitoの設定が追加されています。

amplify_outputs.json
{
  "auth": {
    "user_pool_id": "ap-northeast-3_ULpRA2YO9",
    "aws_region": "ap-northeast-3",
    "user_pool_client_id": "19ep8arft18kequmngk0495cgf",
    "identity_pool_id": "ap-northeast-3:b01cbcaa-5bf3-47f2-8bc3-2b534c3d39ca",
    "standard_required_attributes": [
      "email"
    ],
    "username_attributes": [
      "email"
    ],
    "user_verification_types": [
      "email"
    ],
    "password_policy": {
      "min_length": 8,
      "require_numbers": true,
      "require_lowercase": true,
      "require_uppercase": true,
      "require_symbols": true
    },
    "unauthenticated_identities_enabled": true
  },
  "version": "1"
}

フロントエンド側

認証のバックエンドは完成しましたのでフロントエンド側を修正します。
フロントエンド側は、Gen2になってもこれまでと変わりません。

Amplify UI Libraryを追加します。

npm add @aws-amplify/ui-react

フロントエンドのソースは2ヵ所修正します。

  • src/main.tsx: Amplifyの設定を読み込む記述を追加
  • src/App.tsx: Authenticatorコンポーネントを追加
src/main.tsx
  import React from 'react'
  import ReactDOM from 'react-dom/client'
  import App from './App.tsx'
  import './index.css'
  
+ import '@aws-amplify/ui-react/styles.css'
  
+ import { Amplify } from 'aws-amplify'
+ import outputs from '../amplify_outputs.json'
  
+ Amplify.configure(outputs)
  
  ReactDOM.createRoot(document.getElementById('root')!).render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
  )
src/App.tsx
  import { useState } from 'react'
  import reactLogo from './assets/react.svg'
  import viteLogo from '/vite.svg'
  import './App.css'
  
+ import { Authenticator } from '@aws-amplify/ui-react'
  
  function App() {
    const [count, setCount] = useState(0)
  
    return (
+     <Authenticator>
+       {({ signOut, user }) => (
      <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
+       )}
+     </Authenticator>
    )
  }
  
  export default App

はい、完成です。

バックエンド側のリソース(Cognito)はすでに存在するので、UIでCreate Accountタブからサインアップできます。

関数(Lambda)を追加

バックエンドの作り方がわかってきたと思います。
次はサインインした後に呼び出すhello-worldという名前のLambda関数を作ります。

バックエンド側

amplify/function/hello-world/resource.tsを作成します。authと違い、複数リソースになるため、functionの下に個々のディレクトリがある形式です。

amplify/function/hello-world/resource.ts
import { defineFunction } from '@aws-amplify/backend';

export const helloWorld = defineFunction({
    name: 'hello-world',
    entry: './handler.ts',
})

TypeScriptなので、他にどんなパラメータが設定できるかも簡単に確認できていいですね。

Lambda関数のコードを作成します。LambdaでTypeScriptを使用する際のドキュメント(こちら)を参考に、aws-lambdaライブラリを導入します。

npm add --save-dev aws-lambda

amplify/function/hello-world/handler.tsを作成します。

amplify/function/hello-world/handler.ts
import { Handler } from 'aws-lambda';

export const handler: Handler = async (event, context) => {
    return { message: 'Hello, World!!' }
}

フロントエンドのコードもCDKもLambdaも全部TypeScriptで記述できるのはいいですね!

最後に、amplify/backend.tsに追加します。

amplify/backend.ts
  import { defineBackend } from '@aws-amplify/backend';
  import { auth } from './auth/resource';
+ import { helloWorld } from './function/hello-world/resource'
  
  defineBackend({
      auth,
+     helloWorld,
  });

amplify/backend.tsを保存したタイミングでAWSにLambda作成が開始されます。

もう少し続きます。

Lambdaを呼び出すための権限を付与

Cognitoで認証したユーザーに、このLambdaの実行権限を付与します。

amplify/backend.tsを修正し、まずCognitoで認証した際に付与されるIAMロールを取得します。
そして、「helloWorld関数をInvokeする権限authenticatedUserIamRoleに付与する」という記述を行います。

amplify/backend.ts(抜粋)
- defineBackend({
+ const backend = defineBackend({
      auth,
      helloWorld,
      invokeBedrock,
  });

+ const authenticatedUserIamRole = backend.auth.resources.authenticatedUserIamRole;
+ backend.helloWorld.resources.lambda.grantInvoke(authenticatedUserIamRole);

フロントエンドへの連携

最後に 「amplify_outputs.jsonにLambda関数名(物理名)を出力する」 処理を追加します。
こうすることで、フロントエンド側から、Lambda関数の物理名を参照することができます。

amplify/backend.ts(抜粋)
+ backend.addOutput({
+     custom: {
+         helloWorldFunctionName: backend.helloWorld.resources.lambda.functionName,
+     },
+ });

amplify_outputs.jsonの出力に以下の項目が追加されます。

amplify_outputs.json
{
  ...
  "custom": {
    "helloWorldFunctionName": "amplify-gen2app-node-sand-helloworldlambda89B27E55-2un3X5SixeeW"
  }
}

フロントエンド側

フロントエンド側にLambdaを呼び出す処理を追加します。

まずは必要なimportを追加します。

import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda'
import { fetchAuthSession } from 'aws-amplify/auth'

import outputs from "../amplify_outputs.json"

fetchAuthSession関数でcredentials (アクセスキー、シークレットアクセス、セッショントークン)を取得します。

const { credentials } = await fetchAuthSession()

amplify_outputs.jsonからリージョンとLambda関数名を取得します。

const awsRegion = outputs.auth.aws_region
const functionName = outputs.custom.helloWorldFunctionName

Lambda関数名は先程backend.addOutputで出力した値です

これでLambdaを呼び出す準備が整いました。SDKを使ってLambdaを呼び出します。

const labmda = new LambdaClient({ credentials: credentials, region: awsRegion })
const command = new InvokeCommand({
  FunctionName: functionName,
});
const apiResponse = await labmda.send(command);

if (apiResponse.Payload) {
  const payload = JSON.parse(new TextDecoder().decode(apiResponse.Payload))
  setText(payload.message)
}
この時点のApp.tsx
App.tsx
  import { useState } from 'react'
  import reactLogo from './assets/react.svg'
  import viteLogo from '/vite.svg'
  import './App.css'
  
  import { Authenticator } from '@aws-amplify/ui-react'
+ import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda'
+ import { fetchAuthSession } from 'aws-amplify/auth'
  
+ import outputs from "../amplify_outputs.json"
  
  function App() {
    const [count, setCount] = useState(0)
+   const [text, setText] = useState("")
  
+   async function invokeHelloWorld() {
+ 
+     const { credentials } = await fetchAuthSession()
+     const awsRegion = outputs.auth.aws_region
+     const functionName = outputs.custom.helloWorldFunctionName
+ 
+     const labmda = new LambdaClient({ credentials: credentials, region: awsRegion })
+     const command = new InvokeCommand({
+       FunctionName: functionName,
+     });
+     const apiResponse = await labmda.send(command);
+ 
+     if (apiResponse.Payload) {
+       const payload = JSON.parse(new TextDecoder().decode(apiResponse.Payload))
+       setText(payload.message)
+     }
+   }
  
    return (
      <Authenticator>
        {({ signOut, user }) => (
      <>
        <div>
          <a href="https://vitejs.dev" target="_blank">
            <img src={viteLogo} className="logo" alt="Vite logo" />
          </a>
          <a href="https://react.dev" target="_blank">
            <img src={reactLogo} className="logo react" alt="React logo" />
          </a>
        </div>
        <h1>Vite + React</h1>
        <div className="card">
          <button onClick={() => setCount((count) => count + 1)}>
            count is {count}
          </button>
          <p>
            Edit <code>src/App.tsx</code> and save to test HMR
          </p>
        </div>
        <p className="read-the-docs">
          Click on the Vite and React logos to learn more
        </p>
+       <p>
+         <button onClick={invokeHelloWorld}>invokeHelloWorld</button>
+         <div>{text}</div>
+       </p>
      </>
        )}
      </Authenticator>
    )
  }
  
  export default App
  

ボタンをクリックすると、このようにLambdaのレスポンスを画面表示できました。

関数(Lambda)を追加(発展編)

先程のLambda関数は固定文字を返すだけでしたので、Bedrockを呼び出してその結果を返すLambdaを作成します。

バックエンド側

Lambda関数のコードを作成する前に、Bedrock RuntimeのSDKをインストールします。

npm add --save-dev @aws-sdk/client-bedrock-runtime

amplify/function/invoke-bedrock/resource.tsを作成します。

amplify/function/invoke-bedrock/resource.ts
import { defineFunction } from '@aws-amplify/backend';

export const invokeBedrock = defineFunction({
    name: 'invoke-bedrock',
    entry: './handler.ts',
})

ストリーミングレスポンス

Lambdaは、ストリーミングでレスポンスを返却することができるので、Bedrockから受けたストリーミングのレスポンスをLambdaの結果として返却する方法で実装します。
以下のドキュメントを参考にしました。

amplify/function/invoke-bedrock/handler.ts
import { Context, Handler } from 'aws-lambda';
import { Writable } from 'stream';

import {
    BedrockRuntimeClient,
    InvokeModelWithResponseStreamCommand,
} from "@aws-sdk/client-bedrock-runtime";

type eventType = {
    prompt: string
}

const modelId = "anthropic.claude-3-haiku-20240307-v1:0"

export const handler: Handler = awslambda.streamifyResponse(
    async (event: eventType, responseStream: Writable, _context: Context) => {

        const client = new BedrockRuntimeClient({ region: "us-east-1" });

        const payload = {
            anthropic_version: "bedrock-2023-05-31",
            max_tokens: 1000,
            messages: [
                {
                    role: "user",
                    content: [{ type: "text", text: event.prompt }],
                },
            ],
        };

        const command = new InvokeModelWithResponseStreamCommand({
            contentType: "application/json",
            body: JSON.stringify(payload),
            modelId,
        });

        const apiResponse = await client.send(command);

        if (apiResponse.body) {
            for await (const item of apiResponse.body) {
                if (item.chunk) {
                    const chunk = JSON.parse(new TextDecoder().decode(item.chunk.bytes));
                    const chunk_type = chunk.type;

                    if (chunk_type === "content_block_delta") {
                        const text = chunk.delta.text;
                        responseStream.write(text);
                    }
                } else if (item.internalServerException) {
                    throw item.internalServerException
                } else if (item.modelStreamErrorException) {
                    throw item.modelStreamErrorException
                } else if (item.throttlingException) {
                    throw item.throttlingException
                } else if (item.validationException) {
                    throw item.validationException
                }
            }
        }

        responseStream.end()
    }
)

Bedrockは大阪リージョンにまだ提供されていのないので、バージニア北部のBedrockを呼び出します。

amplify/backend.tsinvoke-bedrockLambda関数を追加します。

amplify/backend.ts(抜粋)
+ import { invokeBedrock } from './function/invoke-bedrock/resource'
  
  defineBackend({
      auth,
      helloWorld,
+     invokeBedrock,
  });

Cognitoで認証したユーザーがこのLambda関数を呼べるように追加します。

amplify/backend.ts(抜粋)
+ backend.invokeBedrock.resources.lambda.grantInvoke(authenticatedUserIamRole);

フロントエンド側が、Lambda関数名を取得できるようにOutputに追加します。

amplify/backend.ts(抜粋)
  backend.addOutput({
      custom: {
          helloWorldFunctionName: backend.helloWorld.resources.lambda.functionName,
+         invokeBedrockFunctionName: backend.invokeBedrock.resources.lambda.functionName,
      },
  });

最後に、Lambda関数がBedrockを呼び出すための権限を設定します。

amplify/backend.ts(抜粋)
+ import * as iam from 'aws-cdk-lib/aws-iam';

+ const bedrockStatement = new iam.PolicyStatement({
+     actions: ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
+     resources: ["arn:aws:bedrock:us-east-1::foundation-model/*"]
+ })

+ backend.invokeBedrock.resources.lambda.addToRolePolicy(bedrockStatement)

このあたりの書き方は、CDKですね。

この時点のamplify/backend.ts
backend.ts
  import { defineBackend } from '@aws-amplify/backend';
+ import * as iam from 'aws-cdk-lib/aws-iam';
  import { auth } from './auth/resource';
  import { helloWorld } from './function/hello-world/resource'
+ import { invokeBedrock } from './function/invoke-bedrock/resource'
  
  const backend = defineBackend({
      auth,
      helloWorld,
+     invokeBedrock,
  });
  
  const authenticatedUserIamRole = backend.auth.resources.authenticatedUserIamRole;
  backend.helloWorld.resources.lambda.grantInvoke(authenticatedUserIamRole);
+ backend.invokeBedrock.resources.lambda.grantInvoke(authenticatedUserIamRole);
  
+ const bedrockStatement = new iam.PolicyStatement({
+     actions: ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
+     resources: ["arn:aws:bedrock:us-east-1::foundation-model/*"]
+ })
  
+ backend.invokeBedrock.resources.lambda.addToRolePolicy(bedrockStatement)
  
  backend.addOutput({
      custom: {
          helloWorldFunctionName: backend.helloWorld.resources.lambda.functionName,
+         invokeBedrockFunctionName: backend.invokeBedrock.resources.lambda.functionName,
      },
  });

フロントエンド側

フロントエンド側にLambdaを呼び出す処理を追加します。
先程のHello Worldとは異なり、パラメータを渡してレスポンスを受け取ります。
レスポンスはストリーミング形式です。

もう殆どAmplify関係なく、通常のReact開発という感じです。

  const [prompt, setPrompt] = useState("")
  const [aiMessage, setAiMessage] = useState("")
  
  async function invokeBedrock() {

    const { credentials } = await fetchAuthSession()
    const awsRegion = outputs.auth.aws_region
    const functionName = outputs.custom.invokeBedrockFunctionName

    const labmda = new LambdaClient({ credentials: credentials, region: awsRegion })
    const command = new InvokeWithResponseStreamCommand({
      FunctionName: functionName,
      Payload: new TextEncoder().encode(JSON.stringify({ prompt: prompt }))
    })
    const apiResponse = await labmda.send(command);

    let completeMessage = ''
    if (apiResponse.EventStream) {
      for await (const item of apiResponse.EventStream) {
        if (item.PayloadChunk) {
          const payload = new TextDecoder().decode(item.PayloadChunk.Payload)
          completeMessage = completeMessage + payload
          setAiMessage(completeMessage)
        }
      }
    }
  }
src/App.tsxの最終形

少し整形したり、undefinedのチェックを追加しています。

src/App.tsx
import { useState } from 'react'
import './App.css'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'

import { Authenticator } from '@aws-amplify/ui-react'
import { InvokeCommand, InvokeWithResponseStreamCommand, LambdaClient } from '@aws-sdk/client-lambda'
import { fetchAuthSession } from 'aws-amplify/auth'

import outputs from "../amplify_outputs.json"


function App() {
  const [text, setText] = useState("")
  const [prompt, setPrompt] = useState("")
  const [aiMessage, setAiMessage] = useState("")

  async function invokeHelloWorld() {

    const { credentials } = await fetchAuthSession()
    const awsRegion = outputs.auth.aws_region
    const functionName = outputs.custom.helloWorldFunctionName

    const labmda = new LambdaClient({ credentials: credentials, region: awsRegion })
    const command = new InvokeCommand({
      FunctionName: functionName,
    });
    const apiResponse = await labmda.send(command);

    if (apiResponse.Payload) {
      const payload = JSON.parse(new TextDecoder().decode(apiResponse.Payload))
      setText(payload.message)
    }
  }

  async function invokeBedrock() {

    const { credentials } = await fetchAuthSession()
    const awsRegion = outputs.auth.aws_region
    const functionName = outputs.custom.invokeBedrockFunctionName

    const labmda = new LambdaClient({ credentials: credentials, region: awsRegion })
    const command = new InvokeWithResponseStreamCommand({
      FunctionName: functionName,
      Payload: new TextEncoder().encode(JSON.stringify({ prompt: prompt }))
    })
    const apiResponse = await labmda.send(command);

    let completeMessage = ''
    if (apiResponse.EventStream) {
      for await (const item of apiResponse.EventStream) {
        if (item.PayloadChunk) {
          const payload = new TextDecoder().decode(item.PayloadChunk.Payload)
          completeMessage = completeMessage + payload
          setAiMessage(completeMessage)
        }
      }
    }
  }

  return (
    <Authenticator>
      {({ signOut, user }) => (
        <>
          <div>
            <a href="https://docs.amplify.aws" target="_blank">
              <img src="https://docs.amplify.aws/assets/icon/favicon-purple-96x96.png" className="logo amplify" alt="Amplify logo" />
            </a>
            <a href="https://vitejs.dev" target="_blank">
              <img src={viteLogo} className="logo" alt="Vite logo" />
            </a>
            <a href="https://react.dev" target="_blank">
              <img src={reactLogo} className="logo react" alt="React logo" />
            </a>
          </div>
          <h1>Amplify + Vite + React</h1>
          <p>
            Hello, {user?.signInDetails?.loginId}
            <br />
            <button onClick={signOut}>Sign Out</button>
          </p>
          <p>
            <button onClick={invokeHelloWorld}>invokeHelloWorld</button>
            <div>{text}</div>
          </p>
          <p>
            <textarea
              onChange={(e) => setPrompt(e.target.value)}
              value={prompt}
              style={{ width: '50vw', textAlign: 'left' }}
            ></textarea>
            <br />
            <button onClick={invokeBedrock}>invokeBedrock</button>
            <div style={{ width: '50vw', textAlign: 'left' }}>{aiMessage}</div>
          </p>
        </>
      )}
    </Authenticator>
  )
}

export default App

これでバックエンド、フロントエンド含めて一通り完成しました。

Gen2のその他の機能

認証と関数以外の機能もあります。

  • Data(AppSync)

  • Storage(S3)

  • Server Side Rendering

本番環境にデプロイ

ローカルでの開発がいい感じに終わったので、本番環境としてデプロイしましょう。

まず、これまでのソースをGitHubにプッシュします。プライベートリポジトリで構いません。

AWSのマネジメントコンソールにて、Amplifyの画面を表示します。新しいアプリボタンをクリックします。

GitHubを選択し、次へボタンをクリックします。

アクセスを許可するウィンドウが表示されるので、対象のリポジトリを選び、Install & Authorizeボタンをクリックします。

Amplifyの画面に戻るので、リポジトリ、ブランチを選択して次へボタンをクリックします。

プロジェクトの自動検出が行われます。このまま次へボタンをクリックします。

確認画面が表示されます。保存してデプロイをクリックします。

しばらくするとデプロイが完了します。

ソースコードがビルドエラーになり、一度デプロイに失敗しましたが、ソースを修正してプッシュすると、自動でビルドが実行されました。

Amplifyが自動でドメインを発行してくれます。発行されたURLにアクセスすると、アプリが確認できます。

また、Amplifyの画面で認証や関数などの管理ができます。

これは結構便利な気がします。

今回は試せてませんがデプロイに関しても色々機能が提供されているようです。

  • Git flow、GitHub/プルリクエストフロー
  • モノレポとマルチリポジトリ


最後まで読んでいただいてありがとうございます。Amplify Gen2に興味を持っていただけたら幸いです。

29
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
21