Amazon Web Services ブログ

フルスタック TypeScript : AWS Amplify を再び紹介

AWS Amplify Gen 2 の一般提供開始を発表できることを嬉しく思います。AWS Amplify Gen 2 は、クラウドに接続されたアプリを構築するためのフルスタックの TypeScript 開発者体験を提供します。AWS Amplify はあなたの 2 つの仕事を手助けします。

  1. Web アプリケーションのホスティング
  2. クラウドバックエンドの構築と接続

Amplify Gen 2 では、アプリのクラウドバックエンドのすべての部分を TypeScript で定義します。認証バックエンドもデータバックエンドもストレージバックエンドもすべてが TypeScript で定義されます

Amplify は AWS によって構築され、AWS 上で動作するため、必要に応じて 200 以上の AWS サービスを追加できます。Amazon Bedrock のような生成 AI サービスも当然、TypeScript で定義することができます。

今週の 1 週間、新しい Gen 2 の機能をローンチウィークの一環として紹介していきます。本日は以下をカバーします。

  1. 新しい Amplify コンソールを使った SSR (Server-Side Rendering) または SPA (Single-Page Application) のデプロイ
  2. 設定不要な認証・認可
  3. フロントエンドアプリでの完全に型安全なクラウドデータ統合
  4. リアルタイムマルチプレイヤーユースケースのための Pub/Sub API

P.S. 過去に Amplify を利用したことがあるが、うまくいかなかった場合は、ぜひ再度試してみてください。皆さまからのフィードバックがきっかけとなり、Gen 2 の開発につながりました

Amplify Gen 2 で構築 : 全く新しい開発者体験

私たちも開発者ですから、新しいものを紹介する最良の方法は一緒に何かを構築することだと分かっています。Amplify を使って全体が TypeScript で記述されたアプリケーションを構築する方法を簡単に説明しましょう。

リアルタイムのマルチプレイアプリを作成し、他のユーザーのカーソル位置をリアルタイムで確認できるようにしましょう。始めるために利用できるサンプルリポジトリを事前に作成しておきました。

Demo of a multiplayer shared cursor application

アプリフロントエンドのクリック操作によるデプロイ

Amplify Hosting を使えば、お気に入りのフレームワークで構築したアプリを、設定をコーディングする必要なくエンドユーザーにデプロイできます。
このデモでは、静的な Vite アプリをデプロイする手順を説明しますが、Amplify Hosting では、SSR アプリ、静的サイト、Next.js、Nuxt、Astro などの人気フレームワークで構築された SPA など様々なタイプの Web アプリケーションをデプロイできます。

Step 1 : この GitHub テンプレートを起点としてリポジトリを新規に作成するにはここをクリックしてください

Demo of how to use a GitHub template

Step 2: こちらをクリックして、クローンした GitHub リポジトリのアプリを Amplify でホストします

Demo of how to host a web app on AWS Amplify

デプロイ中は、リポジトリをローカルにクローンして、ファイルシステムを閲覧することができます。

Demo how to get git clone URL

git clone <YOUR_GITHUB_URL>/amplify-social-room.git

リポジトリの中身を見てみましょう。これはフロントエンドが src/ フォルダ、Amplify Gen 2 のバックエンドが amplify/ フォルダにある標準的な React Vite アプリケーションです。認証リソースは amplify/auth/resource.ts に、データリソースは amplify/data/resource.ts に定義されています。

Amplify の CI/CD パイプラインは、これらの定義ファイルを読み取り、ユーザーの登録とサインインを容易にするクラウドリソースと、データベースコンテンツの作成、読み取り、更新、削除を行うためのリアルタイム API を作成します。

クライアント側からこれらのリソースに接続するには、Amplify のクライアントライブラリを使用します。クライアントライブラリは、バックエンドをデプロイした際の出力を使って構成され、デプロイされた API エンドポイントを把握します。後でこの自動化方法をお見せします。

デプロイされたバックエンドリソースタブに移動し、”Download amplify_outputs.json“を選択してください。

Demo of how to download amplify_outputs.json

amplify_outputs.json ファイルをアプリケーションのリポジトリのルートに移動してください。フォルダ構造は次のようになります。

├── amplify # Your Amplify backend definition files 
│   ├── backend.ts 
│   ├── auth # Auth backend definition 
│   │   └── resource.ts 
│   ├── data # Data backend definition 
│   │   └── resource.ts 
│   ├── package.json 
│   └── tsconfig.json 
├── index.html 
├── package.json 
├── amplify_outputs.json # ADD YOUR amplify_outputs.json HERE 
├── src 
│   ├── App.css 
│   ├── App.tsx 
│   ├── main.tsx 
│   └── ... # other frontend files 
└── ... # other project template files

次に、ターミナルで以下のコマンドを実行し、ローカルに依存関係をインストールします。これには、バックエンドを定義するための依存関係と、バックエンドに接続するためのクライアントライブラリの依存関係が含まれます。

npm install

次に、src/main.tsx ファイルへ移動し、Amplify ライブラリをインポートし、出力ファイルを使用して設定します。

import { Amplify } from 'aws-amplify';
import outputs from '../amplify_outputs.json';

 Amplify.configure(outputs);

AWS はあなたに代わって認証認可を実装します

私たちは皆、認証をゼロから実装するべきではないことを理解しています。それが完全に AWS マネージドの認証サービスと 10 行以下のコードで認証をセットアップできるようになった理由です。Amplify Gen 2 の認証機能を統合することを確認しましょう。amplify/auth/resource.ts ファイルには、メールログインに対応する認証バックエンドがすでに構成されています。

import { defineAuth } from '@ aws-amplify/backend';

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

Auth バックエンドを呼び出す際に、標準の signInsignUp API を使うこともできますが、その場合はログイン UI を全て手作業で構築する必要があります。しかし、もっと簡単な方法があります。Amplify には、UI をすぐに提供する “Authenticator” コンポーネントがあります。UI を完全にカスタマイズして、あなたのブランドと同じ外観にすることもできます。

src/main.tsx ファイルで、Authenticator コンポーネントと、その UI スタイルをインポートし、<App /> コンポーネントを <Authenticator /> コンポーネントでラップしてください。

import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css'
// other imports


Amplify.configure(outputs);

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <Authenticator>
      <App />
    </Authenticator>
  </React.StrictMode>,
)

ターミナルで次のコマンドを実行して、localhost サーバーを起動してください:

npm run dev

これで、機能し、カスタマイズ可能な認証フローが構築されたはずです。ユーザー登録とサインインを試してみてください。

Demo showing the Amplify Authenticator working

リアルタイムクラウド API の作成 : WebSocket、データベース、認証を処理

次はアプリケーションデータを見ていきましょう。最初に基本的な CRUDL (Create/Retrieve/Update/Delete/List) の使用例を確認しましょう。amplify/data/resource.ts ファイルを見ると、”Room” というデータモデルがすでに作成されていることがわかります。

このデータモデルの TypeScript 型をクライアントライブラリ内で再利用します。これにより、フロントエンドがバックエンドの定義と同期したままになります。

const schema = a.schema({
  Room: a.model({
    topic: a.string(),
  }),
})

ユーザーが部屋の一覧を表示したり、新しく部屋を作成できるようにするため、<RoomSelector/> コンポーネントに移動しましょう。

まず、”Data クライアント” を生成します。これにより、バックエンドデータに完全にエンドツーエンドの型安全な方法でアクセスできるようになります。src/RoomSelector.tsx ファイルのインポート文の下に、次のコードを追加してください。

import { type Schema } from "../amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient();

Schema 型は amplify/data/resource.ts から出力される型で、Data クライアントの型の提案が含まれています。Amplify Gen 2 の各データモデルでは、デフォルトでリアルタイム機能が有効になっています。したがって、レコードの作成、更新、削除の際にサブスクリプションイベントを受信できます。

observeQuery() 機能により、これらのイベントを自動的に購読し、ライブリストを返すことができます。RoomSelector の useEffect 関数に次のコードを追加すると、利用可能なすべての部屋のライブリストが選択肢として常に利用できるようになります。

    // set up a live feed inside the useEffect 
    const sub = client.models.Room.observeQuery().subscribe({
      next: (data) => {
        setRooms([defaultRoom, ...data.items])
      }
    })
    return () => sub.unsubscribe()

次に、[ + add] ボタンに onClick ハンドラーを設定します。
部屋が正常に追加されたら、エンドユーザーを新しく作成された部屋に切り替えます。

私の大好きな TypeScript の機能は IntelliSense 自動補完であり、Amplify を TypeScript ベースにした最大の理由の 1 つです。私たちは Gen 2 の重要な部分としてこれを確実に実装することを意図的に行いました。

これを示す GIF がありますが、チュートリアルを進める際には、ぜひご自身で何かを入力し、体験してみてください。

<button />コンポーネントは次のようになるはずです。

    <button onClick={async () => {
      const newRoomName = window.prompt("Room name")
      if (!newRoomName) {
        return
      }
      const { data: room } = await client.models.Room.create({
        topic: newRoomName
      })
      
      if (room !== null) {
        onRoomChange(room.id)
      }
    }}>[+ add]</button>

ブラウザで localhost を開き、今すぐ試してみてください。新しい部屋を作成し、部屋を切り替えてみてください。ドロップダウンオプションが自動的に入力されることがわかります。

Demo of creating and joining a room

クラウドサンドボックス: バックエンドイテレーションのための開発者ごとの個別環境

Amplify Gen 2 では、開発者ごとのクラウドサンドボックスを使用することができ、フロントエンドの localhost インスタンスでより高速なイテレーションを行うことができます。
ローカル開発用に自分のマシンに AWS アカウントを設定する最良の方法は、AWS IAM Identity Center を使うことです。

前提条件: このガイドに従って、ローカル開発用に AWS をマシンにセットアップしてください。

マシンの設定が完了したら、新しいターミナルセッション (npm run dev と同時に実行) を作成し、次のコマンドを実行してクラウドサンドボックスをデプロイしてください。

npx ampx sandbox

初回デプロイの際は、新しいクラウドリソースをデプロイするため、数分かかる可能性があります。
ただし、既存リソースを更新する場合は、ローカルの変更を素早く反映するため、”ホットスワップ” されます。
サンドボックスのデプロイが完了すると、”amplify_outputs.json” の内容が新しいバックエンドエンドポイントに置き換えられます。

私たちは、開発者がいかに高速なデプロイを好むかを知っています。Amplify(の開発チーム) は「リソースのホットスワップ」を可能にするために AWS CDK に 10 以上の Pull Request を作成し、大規模なスキーマのデプロイ速度を 2 倍に短縮しました。

まだまだ改善の余地はありますが、私たちの目標はデプロイ速度の継続的な改善です。

マルチプレイヤー PubSub API を作成してカーソルを共有

amplify/data/resource.ts ファイルで、PubSub インターフェースを作成するための新しい変更とサブスクリプションを追加しましょう。
以下のコードをスキーマに追加してください。

// const schema = a.schema({
  // Room: a.model(...), // Copy/paste the contents below the "Room" model 
  publishCursor: a.mutation()
    .arguments(cursorType)
    .returns(a.ref('Cursor'))
    .authorization(allow => [allow.authenticated()])
    .handler(a.handler.custom({
      entry: './publishCursor.js',
    })),
  
  subscribeCursor: a.subscription()
    .for(a.ref('publishCursor'))
    .arguments({ roomId: a.string(), myUsername: a.string() })
    .authorization(allow => [allow.authenticated()])
    .handler(a.handler.custom({
      entry: './subscribeCursor.js'
    })),
// })
  

これにより、publishCursor mutation と、mutation が呼び出されるたびに subscribeCursor サブスクリプションが定義されます。

次に、mutation と subscription のハンドラーを作成しましょう。新しい amplify/data/publishCursor.js ファイルを作成し、含まれているコンテンツを追加してください。

export const request = () => ({ })

export const response = (ctx) => ctx.arguments

このハンドラは渡された引数をそのまま結果として渡すということを意味し、外部のデータソースを呼び出す必要がありません。

次に、サブスクリプションハンドラファイル amplify/data/subscribeCursor.js を作成します。このファイルには、a/ エンドユーザーが同じルームにいない場合、または b/ エンドユーザー自身のイベントである場合、そのイベントをフィルタリングするロジックを含める必要があります。

import { util, extensions } from '@aws-appsync/utils'
export const request = () => ({ });

export const response = (ctx) => {
    const filter = {
         roomId: { eq: ctx.arguments.roomId },
         username: { ne: ctx.arguments.myUsername }
    }
    extensions.setSubscriptionFilter(util.transform.toSubscriptionFilter(filter))
    return null;
}

では、クライアント側のコードに切り替えましょう。src/CursorPanel.tsx ファイル内で、useEffect にカーソルイベントのサブスクリプションを追加してください。

  useEffect(() => {
    // Add subscriptions here
    const sub = client.subscriptions.subscribeCursor({
      roomId: currentRoomId,
      myUsername: myUsername
    }).subscribe({
      next: (event) => {
        if (!event) { return }
        if (event.username === myUsername) { return }

        setCursors(cursors => {
          return {
            ...cursors,
            [event.username]: event
          }
        })
      }
    })

次に、debouncedPublish関数を更新し、publish ミューテーションを呼び出すようにします。一度に多くのイベントを送信しすぎないように、スロットルロジックを追加しました。

    const debouncedPublish = throttle(150, (username: string, x: number, y: number) => {
      client.mutations.publishCursor({ roomId: currentRoomId, username, x, y })
    }, {
      noLeading: true 
    })

ウィンドウを 2 つ開いて localhost サイトにアクセスすると、ブラウザのウィンドウ間でリアルタイムにカーソルの動きが共有されるはずです。

Demo showing the entire app working together

Git プッシュごとの配信

Amplify は Git ベースの CI/CD ワークフローを使用しているため、このアプリを URL で共有するには、Git にコミットしてオリジンにプッシュするだけで済みます。

git commit -am "added multi-cursor capability"
git push

Amplify コンソールでビルドの準備が整ったことを確認できるようになりました。
準備ができたら、URL を他のユーザーと共有して、このマルチカーソルアプリケーションを試せるようになります。

Demo of opening up the production URL

部屋の中の象※ : Gen1 から Gen2 へのマイグレーションとその課題

デモが終了したところで、皆さんに Amplify Gen 2 をぜひ試していただきたいと思います。現在 Amplify Gen 1 をご利用の皆さまは、どのように移行すればよいのだろうとお考えでしょう。

私たちはまだ継続して、Gen 1 から Gen 2 へのプロジェクトの移行をサポートするためのツール開発を進めています。それまでは、Gen 1 の Amplify プロジェクトでの作業を続けることをお勧めします。Gen 1 と Gen 2 の機能サポート状況の一覧表をこちらに用意しました。当面は Gen 1 と Gen 2 の両方をサポートし続ける予定です。新規プロジェクトでは、拡張された Gen 2 の機能を活用するために、Gen 2 の採用をお勧めします。一方で、Gen 1 のお客様には引き続き、優先度の高いバグ修正や重要なセキュリティアップデートをサポートします。Gen 1 から Gen 2 への移行に関する最新情報は、AWS Amplify on X をフォローするか、Amplify Discord にご参加ください。

※「部屋の中の象」とは、英語の慣用表現で「この場で誰もあえて触れようとしない話題がある」という意味ですが、ここで触れてますし、まだツールが開発中なのでもう少し続報を待ってほしいという意図で筆者は使っているのだと思います(訳者注)

クリーンアップ

  1. AWS Amplify コンソールに移動
  2. アプリケーションへ移動
  3. “App Settings” を選択
  4. “General Setting” を選択
  5. 最後に、”Delete App” を選択

フルスタック TypeScript の Day 1

2020 年に、CSS-Tricks の Chris Coyer が「おっと、私たちは今やフルスタックデベロッパーなのかもしれません」という投稿をしました。スタックには、フロントエンドからバックエンドまで、いくつかの重要な構成要素が含まれています。以下は、Chris がフルスタックの「スペクトル」と表現したものを少し修正したものです。

TypeScript は、フロントエンドからバックエンドまで幅広い分野で人気のプログラミング言語です。TypeScript コミュニティの魅力は、選択肢が事実上無限にあることです。
ユースケースに合わせて、組み合わせたり再構成したり、選んだり調整したり、修正したり、”最適な構成” に繰り返し変更を加えることができます。

TypeScript のすばらしいコミュニティに感謝したいと思います。Next.jsAstroZodArkTypetRPC などのライブラリもぜひチェックしてみてください。これらのライブラリは、フロントエンド開発者がついにフルスタック開発者になれるよう、フルスタック TypeScript の動きを大きく前進させてくれました。

Amplify を全体または一部でも自由にお使いいただけます。フルスタック TypeScript のムーブメントはこれからが本番です。エンドユーザー向けに「まさに適切な」スタックを構築する権限が、あなたの手に委ねられました。Amplify の詳細とその利用方法については、ドキュメントガイドをご覧ください!

本記事は、「Fullstack TypeScript: Reintroducing AWS Amplify」を翻訳したものです。

翻訳者について

稲田 大陸

AWS Japan で働く筋トレが趣味のソリューションアーキテクト。普段は製造業のお客様を中心に技術支援を行っています。好きな AWS サービスは Amazon Location Service と AWS Amplify で、日本のお客様向けに Amazon Location Service の解説ブログなどを執筆しています。