FastAPI with Lambda + API Gatewayでサーバレスアプリケーションの作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/6ec16fbf-ad49-b975-cf6a-642ab0476747.png)

## はじめに
株式会社マイスター・ギルド新規事業部のヒツジーです。
弊社新規事業部では、新規サービスの立ち上げを目指して
日々、アイディアの検証やプロトタイプの作成などを行っています！

技術の進歩は目覚ましいので、置いてかれないように新しい技術のキャッチアップにもいそしんでいます！
本記事では、FastAPI with Lambda + API Gatewayでサーバレスアプリケーションを作成する方法をご紹介します。
サーバレスアプリケーションには
- サーバーの管理が不要
- 使った分だけの支払いで済む
- 柔軟なスケーラビリティ

といったメリットがあり、新規サービスのプロトタイプ作成に向いていると思います。

本記事の手順通りに手を動かすと、さくっとREST APIを作れるようになりますよ！

## やりたいこと
FastAPIで作成したアプリケーションファイルをLambda関数に登録し、API GatewayでLambdaへのルーティングを処理するようにしたい。
これをAPIサーバーとして用意し、フロントエンドからAPIを実行することができるようにしたい。

以降に、そのやり方を具体的に書いていきます。
<img width="400" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/2a10db3d-bf04-ab8b-1c29-adb4992a6d1e.png">

## やることの全体像
１つ1つの手続きは少し面倒で全体像を見失いがちになるので、はじめにやることの全体像をお見せします。

ざっくりいうと、
アプリのファイルをzipファイルに圧縮し、Lambdaにファイルをアップして、API Gatewayの設定をする
だけです。

詳しくは、
1. FastAPIのアプリケーションファイルにMangumのハンドラーを設置
2. FastAPIのアプリケーションファイルをzip圧縮する
3. zipファイルをLambdaにアップロードしてLambda関数を作成し、テストが通るようにする
4. 作成したLambda関数へのルーティングをAPI Gatewayで処理するように設定し、REST APIのエンドポイントを発行する

になります。
以下から詳しい手順の説明になります。

## FastAPIのアプリケーションファイル
FastAPIのアプリケーションファイルは[こちら](https://github.com/NozawaYoshitaka/clean-architecture-like-fastapi/tree/fastapi_for_lambda)を利用します。

`{domain}/api/hoge` のエンドポイントを叩くと下のスクショのように、`{"is_success": true}`が返ってくるようなAPIが実装されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/018f4a6c-58d8-51d4-2f6a-66c1e71802a4.png)
（こちらのアプリケーションファイルの詳しい解説はまたどこかの機会にできたらいいなと思っています。）

FastAPIをLambdaで利用するには、[Mangum](https://mangum.io/)というライブラリーを使う必要があります。

### Mangumとは
Mangumは、AWS Lambdaで[ASGI](https://asgi.readthedocs.io/en/latest/)アプリケーションを実行し、Function URL、API Gateway、Application Load Balancer、Lambda Edgeのイベントを処理するためのアダプタです。

### Mangumの利用方法
```main.py
from mangum import Mangum

app = FastAPI()
handler = Mangum(app)
```
通常利用では、`app = FastAPI()`だけでよいですが、LambdaでFastAPIを使えるようにするために、fastapiのインスタンスをmangumのハンドラーに渡す必要があります 　`handler = Mangum(app)`。

mangum の pip installが済んでいない場合は、しておきましょう。
```bash
$ pip install mangum
```

コードベースの準備はこれだけで完了です。

## Lambda関数の作成
次はLambda関数を用意します。

- Lambdaのサービスページにアクセスします。
- 関数を作成します。
    - 一から作成を選択し、基本的な情報を入力していきます。
        - 関数名を入力
        - ランタイムは「Python 3.9」を選択
        - それ以外はデフォルトのまま（ここはお好みで）
    - ページ右下部の「関数の作成」ボタンを押下します。
<img width="700" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/d8401af3-cd60-a5ea-09de-1bc31691e2e2.png">
<img width="700" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/c8e7eece-cdc6-089b-10cf-ba394d2ea045.png">

これでLambda関数は完成です！

## LambdaにFastAPIのアプリケーションファイルをアップロードする
FastAPIのアプリケーションファイルを圧縮し、zipファイルを生成することでLambdaにアップロードすることができるようになります。
ここでは、圧縮の方法とアップロード方法をお見せします。

### ファイルのzip圧縮方法
Lambdaにアップロードするファイルはzipファイルでないといけないんですが、そのzipファイル内に含めないといけない情報は以下の２つです。
- アプリケーションファイル
- 各種ライブラリのファイル（pip installしてきたライブラリ）

これらのファイルをzipファイルとして1つにまとめるコマンドは以下のようになります。
```bash
$ cd <appフォルダ: clean-architecture-like-fastapi> # root dirに入ります
$ pip install -t ./lib -r ./docker/python/requirements.txt # ライブラリ群をインストールし、libフォルダを作成 & データの格納
$ cd lib # libフォルダ内に入ります
$ zip ../lambda_function.zip -r . # libファイル内のライブラリのファイル群が圧縮され、root dir直下にlambda_function.zipが生成される
$ cd .. # root dirに戻ります

# アプリケーションファイルが多いので以下のようにたくさん追加してます。（アップロードするファイルを一階層上からにすれば、まとめてできるので、実はもっと楽です。）
$ zip lambda_function.zip -u main.py  # main.pyファイルをzipファイルに追加
$ zip lambda_function.zip -u __init__.py  # __init__.pyファイルをzipファイルに追加
$ zip lambda_function.zip -u -r adapters  # adaptersフォルダをzipファイルに追加
$ zip lambda_function.zip -u -r drivers  # driversフォルダをzipファイルに追加
$ zip lambda_function.zip -u -r usecase  # usecaseフォルダをzipファイルに追加
$ zip lambda_function.zip -u -r entities  # entitiesフォルダをzipファイルに追加
$ zip lambda_function.zip -u -r config  # configフォルダをzipファイルに追加
```
`-u`タグはアップデートタグで、zipファイルに追加するときに使います。
`-r`タグはファルダ内の階層深く全ファイルをzipファイルに追加するときに使います。

これでappフォルダ直下に `lambda_function.zip`が生成されたと思います。

`lambda_function.zip`ファイル内はこんな感じで各種ライブラリのインストールファイルが含まれてます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/29611505-cc41-eddd-3e6e-5d7ad9ec7a08.png)


※ zipコマンドが叩けない場合は[こちらの記事](https://qiita.com/Shi-nakaya/items/83d2b2e2b34b897d3df8)を参考にインストールしてください。（ for Windowsユーザー）

## Lambda関数へのファイルアップロードと動作テスト
- 上で作成した`lambda_function.zip`ファイルをアップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/fd8d2c55-fb31-2502-8bec-515b4366305f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/350a0a8b-0748-72cc-3cd1-a107f46c203d.png)


- 動作確認をするために、テストを作成します。
    - 「コード」横の「テスト」をクリック
    - 「新しいイベントを作成」にチェック
    - イベント名を入力
    - テンプレートは「apigateway-aws-proxy」を選択
    - イベントJSON内の`"path": "/path/to/resource",`と`"httpMethod": "POST",`を`"path": "/api/test",`と`"httpMethod": "GET",`に変更する。冒頭部と最下部の２か所にあるので注意
    - 右上の「保存」ボタンをクリックし、現状を保存する
    - 右上の「 テスト」ボタンをクリックし、動作確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/31fe7429-3208-d4f7-a9cd-97bcaf110c24.png)

- テストするとおそらくこんなエラーが出ます。これはハンドラーの指定が間違っていることが原因で起きるよくあるエラーです。
```
{
  "errorMessage": "Unable to import module 'lambda_function': No module named 'lambda_function'",
  "errorType": "Runtime.ImportModuleError",
  ...
}
```

### ハンドラーの修正
- コードページを開く
- ランタイム設定のハンドラーを確認すると `lambda_function.lambda_handler`になっている。
- これを「編集」で`main.handler`に修正します。
→main.pyのhandlerをハンドラー指定しています。つまり上で書いた`handler = Mangum(app)`の部分です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/ffde5443-33cd-ab49-40a1-4b0fa50550eb.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/4ea6f054-032b-f0a8-6633-5201da57b538.png)

- 再度テストを実行したら、このように成功すると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/ecae760a-2739-1acd-56d3-10940b87ce02.png)


## API Gateway側の設定
API Gatewayの設定を行い、Lambda関数へのルーティングを処理できるようにしていきます。

- API Gatewayのサービスページにアクセス
- REST APIで新しいAPIを作成
    - REST APIの「構築」ボタンをクリック
    - プロトコルは「REST」を選ぶ
    - 「新しいAPI」を選択
    - API名を入力
    - 「APIの作成」ボタンをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/69bebfe6-1e82-c670-3243-01361a1dfa46.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/79c3579c-b7d5-dfb3-321e-dc48c4262bc8.png)


- アクション→メソッドの作成→Anyを選択
- /- ANY - セットアップ
    - Lambda関数を選択
    - Lambdaプロキシ統合の使用にチェック
    - Lambda関数に関数名を記入
    - 右下部の「保存」ボタンをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/5a11b1b9-bbde-2127-71db-e772413bd5c3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/05a39690-aad5-8ea7-34d5-9ff9b255a15c.png)

- アクション→リソースの作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/7c5965ae-bebb-00ce-1f97-db3b0ca40165.png)

- リソースの設定
    - プロキシリソースとして設定するにチェック
    - 他はデフォルトのまま
    リソースの作成ボタンをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/8a51b98e-db8e-7438-b386-d7e350446dc3.png)

- /{proxy+} - ANY - セットアップ
    - 統合タイプは「Lambda関数プロキシ」を選択
    - リージョンはLambdaで選択しているリージョンにする（デフォルトで設定されているはず）
    - Lambda関数の名前を入力
    - 右下部の「保存」ボタンをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/b12ae53b-b6ed-4c02-32ff-c2813671f162.png)

- アクション→APIのデプロイ
    - デプロイされるステージは「[新しいステージ]」
    - ステージ名「dev」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/caa3d864-31aa-8fec-814f-3dea6aa23b1b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/2fbb0c1f-88a1-65a9-30eb-53c1e345515c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556658/c74f9584-1682-d077-bffc-3caeba669233.png)


`https://(ここはお見せできない).execute-api.(リージョン).amazonaws.com/dev/api/hoge`

からAPIを叩けるようになりました！

## 最後に
（手順さえ覚えてしまえば、）簡単にREST APIのサーバレスアプリケーションが作成できるようになりました！
FastAPIはさくっとAPIを実装できるので、簡単にアプリを作りたいときには重宝します。
新規サービス開発時にも使えたら使っていきたいと思います！