API GatewayでLambdaを呼び出してCORS対応する方法

# はじめに
こんにちは、もちもちMAXです。
今回はAPI Gatewayでプロキシ統合したLambdaを呼び出す時にCORS対応する方法を説明します。作成するのはREST APIで適当なパラメータをbodyに格納してPOSTできるところまでをやっています。

# 手順
ざっくりとした手順は下記の通りです。
１．API GatewayでLambda関数を紐づけたREST APIを構築
２．Lambda関数にCORS対応を追加
３．CORS対応確認

# Lambda関数の作成
各手順の説明の前に、今回呼び出すAPIの実体であるLambda関数を作成します。
![image (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/463753/97bb1fea-bbf5-6d0b-9f3c-f46a575f60f2.png)

Lambda関数のコードは下記の通りです。
```Python
import json

def lambda_handler(event, context):
    # OPTIONSメソッドを通すための設定
    name = "default"
    if (event['body']) and (event['body'] is not None):
        body = json.loads(event['body'])
        try:
            if (body['name']) and (body['name'] is not None):
                name = body['name']
        except KeyError:
            print('No Name')
    
    return {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json"
        },
        "body": json.dumps("hello" + name)
    }
```
bodyとして渡された文字列に`hello`を付け加えて返すだけのシンプルなLambda関数です。今回はPOSTメソッドを使うのでOPTIONSメソッド用の例外処理を入れています。以降の説明ではこのLambda関数を使っていきます。

# API GatewayでREST APIを構築
AWSのマネジメントコンソールを開きAPI GatewayからREST APIを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/463753/89c13431-baf4-e097-631f-01a8c2ce3e4f.png)

API名と説明を入力して`APIを作成`をクリックします。
![image (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/463753/26c09715-d16e-208b-bbc7-9cec77673eb1.png)

次に、作成したAPIを選択しリソースを作成します。リソース名を適宜入力し、`CORS`にチェックを入れて作成します。ちなみに、プロキシ統合したLambda関数を呼び出す場合はここのCORS設定だけでは上手く機能しません。（未検証ですが、ここのCORS設定は要らないかも）![image (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/463753/1faecc46-f35e-1fda-abd5-d65628f1a9f8.png)

リソースを作成したら次はメソッドを作成します。ここではANYメソッドを作成します。`統合タイプ`はLambda関数で`Lambdaプロキシ統合`をONにして先ほど作成したLambda関数と紐づけます。プロキシ統合をONにすることで、lambda関数側でリクエストパラメータを`event`として取得できるようになります。![image (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/463753/30ba95cf-13d1-0468-de4d-bf01b7426f65.png)

いよいよAPIのデプロイに移ります。APIの画面に移り右上の`APIをデプロイ`をクリックします。初めてデプロイする際にはステージを作成する必要がありますので、`新しいステージ`を選択し適当なステージ名を入力してデプロイします。
![image (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/463753/8173160d-b9c7-9240-1df0-370f308c7e26.png)

以上でAPIの構築は完了です。構築したAPIはステージ画面の`URLを呼び出す`にあるURLで呼び出すことができます。
![image (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/463753/d93a2271-d5a0-9a87-c3fb-9dfb99ded790.png)

# Lambda関数にCORS対応を追加
API GatewayでCORSに対応させるにはLambda関数にコードを追加する必要があります。最初に作成したLambda関数をベースに変更したコードは下記の通りです。
```Python
import json

def lambda_handler(event, context):
    # OPTIONSメソッドを通すための設定
    name = "default"
    if (event['body']) and (event['body'] is not None):
        body = json.loads(event['body'])
        try:
            if (body['name']) and (body['name'] is not None):
                name = body['name']
        except KeyError:
            print('No Name')

    return {
        "statusCode": 200,
        "headers": {
            # CORS対応のためにAccessで始まる行を追加
            "Access-Control-Allow-Headers": "Content-Type",
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Methods": "OPTIONS,POST,GET",
            "Content-Type": "application/json"
        },
        "body": json.dumps("hello" + name)
    }
```
CORS対応のために`headers`を3行追加しています。Lambda関数側の対応はここだけです。

# CORS対応確認
CORS対応ができているかどうか確認するために簡単なWebアプリを作成しました。Vue（3系）とTailwind CSSで作成しています。URLを作成したAPIのものに変更して実行すれば、入力フォームの文字列＋helloが返ってきます。

```vue
<!-- App.vue -->
<template>
  <div class="container mx-auto p-4">
    <h1 class="text-3xl font-bold mb-4">Chat App</h1>
    <div class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="prompt">
          Prompt
        </label>
        <input
          class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
          id="prompt"
          v-model="prompt"
          type="text"
          placeholder="Enter your prompt"
        />
      </div>
      <div class="flex items-center justify-between">
        <button
          class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
          type="button"
          @click="sendPrompt"
        >
          Send
        </button>
      </div>
    </div>
    <div class="bg-white shadow-md rounded px-8 pt-6 pb-8">
      <h2 class="text-xl font-bold mb-4">Chat History</h2>
      <div v-if="loading" class="text-center">
        <p class="text-gray-600">Loading...</p>
      </div>
      <div v-else>
        <div v-for="(message, index) in chatHistory" :key="index" class="mb-4">
          <p class="font-bold" :class="{ 'text-blue-600': message.isUser, 'text-green-600': !message.isUser }">
            {{ message.isUser ? 'You' : 'AI' }}:
          </p>
          <p class="text-gray-700">{{ message.text }}</p>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';
const prompt = ref('');
const chatHistory = ref([]);
const loading = ref(false);
async function sendPrompt() {
  if (!prompt.value.trim()) return;
  chatHistory.value.push({ text: prompt.value, isUser: true });
  loading.value = true;
  try {
    // fetch先のURLを作成したAPI Gatewayの値に変える
    const response = await fetch('https://your_api', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ name: prompt.value })
    });
    if (!response.ok) {
      throw new Error('API request failed');
    }
    const data = await response.json();
    chatHistory.value.push({ text: data.result, isUser: false });
  } catch (error) {
    chatHistory.value.push({ text: 'Error occurred while fetching response.', isUser: false });
  } finally {
    loading.value = false;
    prompt.value = '';
  }
}
</script>
```

それでは早速APIをコールしてみましょう。`mochi`をbodyに入力してコールすると`hellowmochi`と返ってきてますね。想定通りCORSエラーも発生せず動いています。
![image (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/463753/ab299082-92d6-e197-6a1c-2325a4464049.png)

ちなみに、CORS対応を入れる前にAPIをコールすると添付の通りエラーをはいてしまいます。
![image (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/463753/e70c595d-83f7-c40a-be8d-a317794d0235.png)

# さいごに
本記事では、API GatewayでLambda関数を呼び出してCORS対応する方法を説明しました。基本的にはLambdaのresponseにheadersを追加するだけなので難しくないですね。それではまた！

最近、ブログと自作アプリのLPを立ち上げたので是非見てってください！

ブログ↓
https://mochinochikimchi.com

アプリLP↓
https://mochinochikimchi.com/applications/mealmotion/index.html