AWS&TypeScriptでサーバーレスAPIを構築①

## はじめに
AWSを用いて個人開発用のバックエンドを構築したのでベストプラクティスを記しておこうと思います。サンプルは以下に。

https://github.com/kmatsushita1012/my-api
## 概要
単純なユーザーデータを扱うDBを構築するものとします。
- 単一取得(GET)
- 全件取得(GET)
- 新規登録(CREATE)
- 更新(UPDATE)
- 削除(DELETE)

また、Gitのpush時に自動でビルドとデプロイを行います。(CI/CD)
使用するサービスやライブラリは以下の通り
- AWS
    - APIGateWay
    - Lambda
    - DynamoDB
    - CodePipeLine
    - CodeBuild
- TypeScript
    - Node.js
    - express
    - aws-sdk
## 作業
### Node.jsプロジェクトの作成
1. 下記コマンドを実行
    ```
    mkdir <プロジェクト名>
    cd <プロジェクト名>
    npm init -y
    npx tsc --init
    mkdir src
    touch src/index.ts
    touch README.md
    touch .gitignore
    ```
1. `tsconfig.json`を以下のように編集
    ```tsconfig.json
    {
      "compilerOptions": {
        "target": "ES2020",
        "module": "commonjs",
        "outDir": "./dist",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
      },
      "include": ["src/**/*"],
      "exclude": ["node_modules"]
    }
    ```
1. `.gitignore`を以下のように編集
    ```./gitignore
    node_modules
    dist
    ```
4. `src/index.ts`を以下のように編集
    ```src/index.ts
    import express from "express";
    import serverless from "@vendia/serverless-express";
    
    const app = express();
    app.use(express.json());
    app.get("/", (req, res) => {
      res.send("Hello");
    });
    export const handler = serverless({ app });
    ```


### Gitの初期化
[GitHub](https://github.com/)で新しいリポジトリを作成してください。(Add README.mdは選ばない）
```
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/<ユーザー名>/<プロジェクト名>.git
git push -u origin main
```
### ライブラリの追加
`.gitignore`で`node_modules`を設定してないとファイルが多すぎてリモートがパンクするかもなので後から行いました。
```
npm install --save-dev typescript ts-node @types/node
npm i express @vendia/serverless-express
```

### CodePipelineを作成
1. AWSのコンソールからCodePipelineにアクセス
1. 右上から新規作成
1. カスタムパイプラインの構築を選択
    ![スクリーンショット 2025-04-22 18.06.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994994/272d6141-671b-4013-abdb-9564df1b6eaf.png)
1. Step2の必要事項を入力
    ![スクリーンショット 2025-04-22 18.07.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994994/085a3cd8-e91d-4a05-b869-25208d00f9f0.png)
1. Step3でGitHubに連携(アプリ経由を選択し後はデフォルト通りに進む)
    ![スクリーンショット 2025-04-22 18.08.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994994/92c39bcc-df96-4b08-9a20-69feb4672603.png)
1. Step4でその他ビルドプロバイダーを選択
    - "プロジェクトを作成する"からポップアップへ
    - "buildspecファイルを使用する"を選択
    ![スクリーンショット 2025-04-22 18.16.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994994/20f3a77a-e877-4397-bd12-9c609b080cb5.png)
1. Step5でテストステージはスキップ
1. Step6でデプロイステージもスキップ
    - 本当はデプロイステージでデプロイすべきだがうまく連携が取れなかったのでbuildステージでデプロイする
1. Step7で作成

 ### Lambdaを作成
1. AWSのコンソールからLambdaにアクセス
1. 右上から新規作成
1. ランタイムで"Node.js 22.x"を選択
    ![スクリーンショット 2025-04-22 18.21.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994994/2e816da5-ed71-4d2f-a95a-7bd00cb6097f.png)
1. 右下から作成
1. "エイリアス"タブから"エイリアスを作成"を選択
    ![スクリーンショット 2025-04-23 14.48.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994994/50095295-4fab-4b21-a06d-2eaa38a69c4f.png)
1. エイリアスを作成
    - 名前は"dev"
    - バージョンは`$LATEST`
    ![スクリーンショット 2025-04-23 14.52.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994994/25b957da-5998-4b0a-8178-1e45b2b69a83.png)
    
### APIGateWayの作成
1. AWSのコンソールから APIGateWayにアクセス
1. 右上から新規作成
1. "HTTP API"を選択
1. APIの設定を行う
    - 名前を入力
    - 統合から作成したLambdaを選択
    - バージョンを**1.0**に(重要)
    ![スクリーンショット 2025-04-23 12.49.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994994/a96d7f84-476d-4846-aebc-47ed8232ad88.png)
1. ルートを設定
    - リソースパスは`/{proxy+}`に置き換え
    ![スクリーンショット 2025-04-23 12.51.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994994/02dc6bf3-6ad5-4fdc-8080-d57b7a0590ff.png)
    - ステージを追加で"dev"を追加
        - 必須ではないですが環境を分けたいので
        - 自動デプロイはオフ
1. 作成

### IAMを編集
AWS上での権限を編集します。

1. コンソールからIAMにアクセス
1. 左のメニューからロールを選択
1. ロールから`codebuild`が含まれるものを選択
![スクリーンショット 2025-04-23 14.38.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994994/7e1eee72-1cd1-480b-a334-ef682d0c9b87.png)

1. 右の"許可を追加"を選択し"ポシリーをアタッチ"を選択
1. 以下のポリシーを選択して追加
    - AWSLambda_FullAccess
    - AmazonAPIGatewayAdministrator
1. 同様にロールから`lambda`が含まれるものを選択し以下を追加
    - AmazonDynamoDBFullAccess

 ### ビルドワークフローの作成
 エディタでの作業に戻ります。
 1. ワークフローファイルの作成
     ```
     touch buildspec.yml
    ```
1. 編集
    ```buildspec.yml
    version: 0.2
    
    env:
      variables:
        FUNCTION_NAME: "my-api"
        ALIAS_NAME: "dev"
        STAGE_NAME: "dev"
        API_NAME: "my-api"
    
    phases:
      install:
        commands:
          - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
          - unzip -q awscliv2.zip
          - ./aws/install --install-dir ~/aws-cli --bin-dir ~/bin --update
          - export PATH=~/bin:$PATH
          - npm install
          - npm install -g typescript
    
      pre_build:
        commands:
          - echo No Pre Build phase
    
      build:
        commands:
          - echo Build started on `date`
          - tsc
          - CurrentVersion=$(aws lambda get-alias --function-name $FUNCTION_NAME --name $ALIAS_NAME --query 'FunctionVersion' --output text)
          - echo "Zipping deployment package"
          - mkdir -p deploy-package
          - cp -r dist/* deploy-package/
          - cp -r node_modules deploy-package/
          - cd deploy-package
          - zip -q -r ../lambda.zip .
          - cd ..
          - aws lambda update-function-code --function-name $FUNCTION_NAME --zip-file fileb://lambda.zip --debug
          - sleep 10
    
      post_build:
        commands:
          - echo Build completed on `date`
          - ls
          - aws lambda publish-version --function-name $FUNCTION_NAME --description "update version"
          - TargetVersion=$(aws lambda list-versions-by-function --function-name $FUNCTION_NAME --query 'Versions[-1].Version' --output text)
          - echo $CurrentVersion
          - echo $TargetVersion
          - LC_ALL=C.UTF-8 aws lambda update-alias --function-name $FUNCTION_NAME --name $ALIAS_NAME --function-version $TargetVersion
          - |
            if [ "$CurrentVersion" != "$TargetVersion" ]; then
              aws lambda delete-function --function-name $FUNCTION_NAME --qualifier $CurrentVersion
            fi
          - echo "Deploying API Gateway"
          - API_ID=$(aws apigatewayv2 get-apis --query "Items[?Name==\`$API_NAME\`].ApiId" --output text)
          - aws apigatewayv2 create-deployment --api-id $API_ID --stage-name $STAGE_NAME
          - echo "Deployment completed for HTTP API $API_ID at stage $STAGE_NAME"
    ```

### 完成(仮)
1. GitHubに差分をpush
1. デフォルトエンドポイントにアクセス
    - 右上の黒四角のURLにアクセス
![スクリーンショット 2025-04-23 15.32.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994994/e5b5b3fb-57c6-4b5d-b3d5-e89f23197fab.png)
    - `src/index.ts`で設定した"Hello"が表示される
![スクリーンショット 2025-04-23 15.37.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3994994/b6e007df-cbcb-4595-91e4-c6a055edecea.png)

## まとめ
今回はプロジェクトを作成して自動でデプロイされるまでを作成しました。次回はレイヤードアーキテクチャで中身を作成していきます。

https://qiita.com/kmatsushita1012/items/34b49ff1c824e28765ba

## 参考記事

https://qiita.com/akihiro-moriwaki/items/40547ff36d7744148822

https://www.ctc-g.co.jp/solutions/cloud/column/article/72.html

https://qiita.com/sadnessOjisan/items/ea5590efa3f55ef56edd

https://qiita.com/Michinosuke/items/879cfe47ca1aeae2c084