Amplify Gen2 からは TypeScript でバックエンド開発をしよう

# はじめに
[AWS Amplify Advent Calendar 2024](https://qiita.com/advent-calendar/2024/amplify) の7日目の記事です。

個人的に Amplify が好きなので、これまで(*1)も Amplify 関連記事を書いてきました。今年は Amplify Gen2 のバックエンド周りの情報を整理していきたいと思います。


（*1）

https://qiita.com/t-kurasawa/items/167d4e25410e2c6a92f7


https://qiita.com/t-kurasawa/items/0d688b85ef7f9136e23b


https://qiita.com/t-kurasawa/items/152bbda746e4658c3fc9


https://qiita.com/t-kurasawa/items/3275d37053f4b0bea983


https://qiita.com/t-kurasawa/items/23ee71341df6b37ff7cc


# Amplify Gen2 とは

Amplify は2024年5月に Gen1（第一世代）から Gen2（第二世代）に進化しました。Gen2 で進化したポイントは以下の４つです。

__TypeScript フルスタック開発__
フロントとバック両方を TypeScript, AWS CDK でコード化

__Amplify コンソールで統合管理__
ビルド、ホスティング、環境変数、シークレット、データ、認証、ストレージ

__開発者別のサンドボックス環境__
Gen1 ではチーム共有環境だったものが、開発者ごとの独立環境へ

__Git ベースの環境管理__
ブランチに応じた本番・ステージング・プレビュー環境

## TypeScript によるフルスタック構成

フロントエンドとバックエンド両方を TypeScript で型安全・宣言的に定義していくことができます。

データモデル（DynamoDB）、API（AppSync）、認証・認可（Cognito）、ストレージ（S3）、関数（Lambda）を TypeScript で宣言的に定義すると AWS リソースが自動生成されます。また AWS CDK を TypeScript で記述した IaC でクラウドインフラを構築・管理できます。そして、このバックエンドと React, Vue.js などのモダンフロントエンドを接続することができます。


## 手を動かして理解を深める

[AWS Summit Japan 2024 で Gen2 に触発されて書いた記事](https://qiita.com/t-kurasawa/items/167d4e25410e2c6a92f7#aws-amplify-gen2-%E3%81%A8%E3%81%AF)の続きから手を動かして理解を深めていきたいと思います。

### Auth

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/73b48470-1a0e-7188-ae10-2d8c2ef154fb.png)

__バックエンド__
メールログインでリソース定義します。

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

/**
 * Define and configure your auth resource
 * @see https://docs.amplify.aws/gen2/build-a-backend/auth
 */
export const auth = defineAuth({
  loginWith: {
    email: true,
  },
});
```

__フロントエンド__
UI コンポーネントで `Authenticator` が用意されているので利用します。

```src/App.tsx
import { Authenticator } from '@aws-amplify/ui-react'
import '@aws-amplify/ui-react/styles.css'

function App() {
...省略...

  return (
    <Authenticator>
      {({ signOut }) => (
        <main>
          <h1>My todos</h1>
          ...省略...
          <button onClick={signOut}>Sign out</button>          
        </main>
      )}
    </Authenticator>
  );
}
export default App;
```

### Data

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/091ee63a-4938-69f7-4398-c41bb84f5fe8.png)


__バックエンド__
DynamoDB のデータモデル定義をします。

```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",
    // API Key is used for a.allow.public() rules
    apiKeyAuthorizationMode: {
      expiresInDays: 30,
    },
  },
});

```


__フロントエンド__


```src/App.tsx

import { Authenticator } from '@aws-amplify/ui-react'
import '@aws-amplify/ui-react/styles.css'
import { useEffect, useState } from "react";
import type { Schema } from "../amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

function App() {

  ...データ操作はこの辺りの実装です....
  const [todos, setTodos] = useState<Array<Schema["Todo"]["type"]>>([]);

  useEffect(() => {
    client.models.Todo.observeQuery().subscribe({
      next: (data) => setTodos([...data.items]),
    });
  }, []);

  function createTodo() {
    client.models.Todo.create({ content: window.prompt("Todo content") });
  }

  function deleteTodo(id: string) {
    client.models.Todo.delete({ id })
  }
  ...データ操作はこの辺りの実装です....


  return (
    <Authenticator>
      {({ signOut }) => (
        <main>
          <h1>My todos</h1>

          ...データ操作はこの辺りの実装です....
          <button onClick={createTodo}>+ new</button>
          <ul>
            {todos.map((todo) => (
              <li
                key={todo.id}
                onClick={() => deleteTodo(todo.id)}
              >{todo.content}</li>
            ))}
          </ul>
          ...データ操作はこの辺りの実装です....

          <button onClick={signOut}>Sign out</button>
        </main>
      )}
    </Authenticator>
  );
}

export default App;
```


### Storage

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/52a4dc5e-037f-2071-8b5d-0e866b028cc0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/3d2ecdc1-c041-c8ee-9dd3-bee302c74b15.png)



__バックエンド__
S3 のリソース設定をします。

```amplify/storage/resource.ts
import { defineStorage } from '@aws-amplify/backend';

export const storage = defineStorage({
  name: 'amplifyTeamDrive',
  access: (allow) => ({
    'profile-pictures/{entity_id}/*': [
      allow.guest.to(['read']),
      allow.entity('identity').to(['read', 'write', 'delete'])
    ],
    'picture-submissions/*': [
      allow.authenticated.to(['read', 'write']),
      allow.guest.to(['read', 'write'])
    ],
  })
});

```

__フロントエンド__


```src/App.tsx

import { Authenticator } from '@aws-amplify/ui-react'
import '@aws-amplify/ui-react/styles.css'
import { useEffect, useState } from "react";
import { uploadData } from 'aws-amplify/storage';

function App() {
  ...Storage操作はこの辺り...
  const [file, setFile] = useState<File | undefined>();

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files;
    if (files) {
      setFile(files[0]);
    }
  };

  const handleUpload = () => {
    if (file) { // undefined チェック
      uploadData({
        path: `picture-submissions/${file.name}`,
        data: file,
      })
      alert(`${file.name} がアップロードされました`)
    } else {
      alert(`ファイルが選択されていません`)
    };
  };
  ...Storage操作はこの辺り...

  return (
    <Authenticator>
      {({ signOut }) => (
        <main>
          <h1>My todos</h1>
          ...Storage操作はこの辺り...
          <div>
            <input type="file" onChange={handleChange} />
            <button
              onClick={handleUpload}>
              Upload
            </button>
          </div>
          ...Storage操作はこの辺り...
          <button onClick={signOut}>Sign out</button>          
        </main>
      )}
    </Authenticator>
  );
}

export default App;
```

# おわりに
今回は Auth, Data, Storage 周りをさらっと触りました。他にも Function 等もあり、まだまだ深堀りがいがありそうですね。

それでは良いお年を。