AWS Amplify Gen2 が 2024年 5月 に一般提供開始となりました (アナウンス)。いい機会ですので手元でちょっとしたデモアプリを作成するために、Amplify Gen2 も使ってみようと思いました。本記事ではかなり初歩的とは思いますがマニュアルインストールした際の手順を記録しておきたいと思います。

Amplify そのもの内容についてはほとんど触れませんのであしからず 🙇‍♂️

はじめに

公式ドキュメントから辿ると、“Get started” に “Quickstart” があり、テンプレートから簡単に始められそうなのですが、手元 (localhost) でちょっと試すために GitHub リポジトリ作るのものな…と思いましたので、マニュアルインストールをしていきます。

“Get started” には “Manual installation” もあるのですが、純粋に Amplify のインストールだけであり、React などのセットアップには言及されていません。この界隈に疎い私はいくつかエラーに遭遇したのでその記録を残しておきたいと思います。

おおまかな手順としてはこちらの Qiita の投稿を参考にさせていただきました。

ちなみに最近は npm ではなく pnpm を使っていますのでご了承ください。

React プロジェクトの作成

まずはじめに React プロジェクトを作っていきます。昨今は Vite というもので React プロジェクトが作れるそうです。create-react-app はもう公式でも案内されていないとか。

pnpm create vite@latest demo-app -- --template react-ts

実行すると demo-app というディレクトリを作成し、その中に React プロジェクトを作成します。カレントディレクトリに作成したい場合は demo-app 部分を . にします。

フレームワークを選択します。今回は React を選択します。

? Select a framework: › - Use arrow-keys. Return to submit.
    Vanilla
    Vue
❯   React
    Preact
    Lit
    Svelte
    Solid
    Qwik
    Others

TypeScript のつもりでしたが、SWC というのも出てきました。SWC は Babel などよりかなりパフォーマンス改善がされているそうです。良さそうなので選択してみます (よくわかっていない)。

? Select a variant: › - Use arrow-keys. Return to submit.
    TypeScript
❯   TypeScript + SWC
    JavaScript
    JavaScript + SWC
    Remix ↗

必要なものをインストールして Web アプリを立ち上げます。

cd demo-app
pnpm install
pnpm run dev

以下のように表示されたら、ブラウザで http://localhost:5173 を開いてみます。

❯ pnpm run dev

> demo-app@0.0.0 dev /tmp/demo-app
> vite

Port 5173 is in use, trying another one...

  VITE v5.2.12  ready in 415 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

Initial React App screen with Vite

良さそうです。

Amplify Gen2 のセットアップ

続いて本題の Amplify Gen2 をインストール、セットアップしていきます。

Amplify Gen2 インストール

React プロジェクトのルートで (demo-app ディレクトリの中で) 以下のコマンドを実行します。

pnpm install -D \
  @aws-amplify/backend@latest \
  @aws-amplify/backend-cli@latest

Amplify リソース関連のファイルを作成

Amplify で必要なファイルなどを作成していきます。プロジェクトのルートディレクトリで、amplify ディレクトリを作成します。

mkdir amplify

amplify/backend.ts という TypeScript ファイルを作成し、以下のように記述します。

1
2
3
4
import { defineBackend } from '@aws-amplify/backend';

defineBackend({
});

サンドボックスの起動

(npm の場合は npx ampx sandbox で起動します。)

pnpm exec ampx sandbox

エラーとなりました。。。

    :
  (snip)
    :
undefined
 ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL  Command "tsx" not found

Did you mean "pnpm tsc"?

Error: Subprocess exited with error 254

Caused By: Subprocess exited with error 254

tsx コマンドが見つからないとのことなのでインストールします。

pnpm install -D tsx

あらためてサンドボックスを起動します

pnpm exec ampx sandbox

(CDK を使ったことない場合は、ブートストラップを完了するよう URL が表示されるようです。ガイドに従ってくださいませ。)

警告なども少し出ますが、以下の状態のまま実行中になれば無事サンドボックスは起動できています。

    :
  (snip)
    :
✨  Total time: 11.92s


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

セットアップとしては以上です。この段階ではリソースを何も定義していないので、サンドボックス、すなわち AWS クラウド側にもリソースは作成されていません (CloudFormation スタックはできていますがメタデータしかありません)。

今後用途に応じて追加していくであろうパッケージは以下のようなものが一例になるでしょうか。

  • dependencies
    • UI 関連
      • aws-amplify
      • @aws-amplify/ui-react
    • SDK 関連 (xxx は AWS サービス名 (例: s3))
      • @aws-sdk/client-xxx
    • SigV4 でリクエストするとか (参考 : Call a GraphQL API from a Lambda function)
      • @aws-crypto/sha256-universal
      • @aws-sdk/credential-provider-node
      • @smithy/protocol-http
      • @smithy/signature-v4
      • node-fetch
  • devDependencies
    • Amplify のスコープ外のリソース追加のため
      • aws-cdk-lib

Lambda 関数の追加

いきなり Lambda を追加するのも変なのですが、Lambda 関数を追加した際にエラーが出てしまったのでトラブルシュートの記録として残しておきたいと思います。

プロジェクトのルートフォルダから amplify/function/my-function/resource.ts を作成し、以下のように記述します。

1
2
3
4
5
6
import { defineFunction } from '@aws-amplify/backend';

export const myFunction = defineFunction({
    name: 'my-function',
    entry: './handler.ts',
});

続いて entry: './handler.ts', とあるように Lambda 関数の実態を amplify/function/my-function/handler.ts に作成します。
(ちなみに、handler.ts という名前でファイルを作成した場合は、entry: './handler.ts', の記述を省略できます。)

1
2
3
4
5
import { Handler, Context } from 'aws-lambda';

export const handler: Handler = async (event, context: Context) => {
    return { message: 'My Function!!' }
}

参照している依存も追加しておきます。

pnpm install -D aws-lambda @types/aws-lambda

冒頭で作成した amplify/backend.ts に、作成した Lambda 関数を追加します (2, 5行目を追加)。

1
2
3
4
5
6
import { defineBackend } from '@aws-amplify/backend';
import { myFunction } from './function/my-function/resource';

defineBackend({
  myFunction
});

すると、サンドボックスで以下のようなエラーが出てしまいました (見やすさのため改行させています。実際は 1行でフラットに出力)。

Failed to instantiate nodejs function construct
Caused By: Failed to bundle asset amplify-demoapp-XXX-sandbox-e7d4f2307f/function/my-function-lambda/Code/Stage, 
bundle output is located at /Users/XXX/tmp/demo-app/.amplify/artifacts/cdk.out/bundling-temp-XXXXXXXX-error: 
Error: bash -c pnpm exec -- esbuild --bundle "/Users/XXX/tmp/demo-app/amplify/function/my-function/handler.ts" 
--target=node18 --platform=node --format=esm 
--outfile="/Users/XXX/tmp/demo-app/.amplify/artifacts/cdk.out/bundling-temp-XXXXXXXX/index.mjs" 
--external:@aws-sdk/* --loader:.node=file 
--banner:js="
/**
 * Reads SSM environment context from a known Amplify environment variable,
 * fetches values from SSM and places those values in the corresponding environment variables
 */
export const internalAmplifyFunctionResolveSsmParams = async (client) => {
  const envPathObject = JSON.parse(process.env.AMPLIFY_SSM_ENV_CONFIG ?? '{}');
  const paths = Object.keys(envPathObject);
  if (paths.length === 0) {
    return;
  }
  let actualSsmClient;
  if (client) {
    actualSsmClient = client;
  }
  else {
    const ssmSdk = await import('@aws-sdk/client-ssm');
    actualSsmClient = new ssmSdk.SSM();
  }
  const resolveSecrets = async (paths) => {
    const response = await actualSsmClient.getParameters({
        Names: paths,
        WithDecryption: true,
    });

    if (response.Parameters && response.Parameters.length > 0) {
      for (const parameter of response.Parameters) {
        if (parameter.Name) {
          const envKey = Object.keys(envPathObject).find((key) => envPathObject[key].sharedPath === parameter.Name);
          const envName = envKey? envPathObject[envKey].name : envPathObject[parameter.Name]?.name;
          process.env[envName] = parameter.Value;
        }
      }
    }
    return response;
  };
  const response = await resolveSecrets(paths);
  const sharedPaths = (response?.InvalidParameters || [])
    .map((invalidParam) => envPathObject[invalidParam].sharedPath)
    .filter((sharedParam) => !!sharedParam);
  if (sharedPaths.length > 0) {
    await resolveSecrets(sharedPaths);
  }
};
await internalAmplifyFunctionResolveSsmParams();
const SSM_PARAMETER_REFRESH_MS = 1000 * 60;setInterval(() => {
  void internalAmplifyFunctionResolveSsmParams();
}, SSM_PARAMETER_REFRESH_MS);
export {};"
--inject:"/Users/XXX/tmp/demo-app/node_modules/.pnpm/@aws-amplify+backend-function@1.0.3_@aws-sdk+types@3.577.0_aws-cdk-lib@2.144.0_constructs@10._uc5ex6achjb4y3focymv7hus6i/node_modules/@aws-amplify/backend-function/lib/lambda-shims/cjs_shim.js" 
run in directory /Users/XXX/tmp/demo-app exited with status 254

Resolution: See the underlying error message for more details.

Error: bash -c pnpm exec -- esbuild ... というところに着目して、 esbuild をインストールしたところエラーなく Lambda 関数がデプロイできました。

pnpm install -D esbuild

サンドボックスの削除

サンドボックスは AWS クラウド側では CloudFormation スタックとして存在しています。ローカル側でサンドボックスのプロセス (ampx) を終了させても、CloudFormation スタックが消えるわけではありません。スタックを削除するには以下のコマンドを実行します。

pnpm exec ampx sandbox delete

確認が入るので “y” を押します。

? Are you sure you want to delete all the resources in your sandbox environment (This can't be undone)? (y/N)

まとめ

vite を使って Amplify Gen2 のプロジェクトをセットアップしてみました。私の環境では tsx や esbuild などの追加も必要でした。同じようなエラーに遭遇した方のお役に立てたら幸いです。

最後に・・・

この投稿は個人的なものであり、所属組織を代表するものではありません。ご了承ください。