Qiita AdventCalender2024に投稿してクリスマスツリーを成長 & 飾り付けさせよう! Part1 バック & AWS デプロイ編[QiitaAPIを使った簡単なHTTPAPIをLambda & APIGatewayにデプロイしよう]

みなさんついにQiitaAdventCalender2024が始まりました！私はここから25日間毎日記事投稿する...というわけですが(吐きそう)...12/1でDay1ということで何を書こうかなと考え，12/1の朝にQiitaAdventCalender2024に関連した作品を作ろうと思いました．しかし12/1の朝に思い立ったのでなんとかして1日でフロントエンドとAPI叩く用のLambda関数を作成し，フロントの静的ファイルをS3にホスティングしてCloudFrontで配信し，Lambda関数をAPIGatewayで受け付けるデプロイ処理もしつつ，この記事も書かないといけないのでガバもあると思います．その際は遠慮なくコメントで突っ込んでください．12/25までにより良くしていこうと思います．また，作品の制作レポートだけではちょっとなんとも言えないのでAWS LambdaとAmazon API Gatewayを使用して，Qiita APIと連携するシステムの構築方法について解説もしようと思う．

**完成(?)画像**

![スクリーンショット 2024-12-01 22.28.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/e8d29436-f6c4-62c1-2e29-9b4a1317f188.png)

> まだ1記事もないのでまっさら(植木鉢)のみですが25記事投稿したらすごいことになる...はずです．12/25に完成したシステムのレポートとフロント部の解説をします．今回はバックエンド(Lambda関数)部分のみです．

## 作ったシステムの概要

本システムは以下の機能を提供する．

- 指定したユーザーIDの記事を検索
- 2024年12月1日から12月25日までの期間で投稿された記事を取得
- 記事のタイトル，URL，投稿日時などの情報を返す
- 後にこのレスポンスをフロントでどう使うか考える

> 一旦フロントでは記事の数をカウントするのみにします

AWSアーキテクチャはこんな感じです
![スクリーンショット 2024-12-02 0.35.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/15a66b28-05c2-c157-fa64-d85600e3ef47.png)


## 実装手順

**1. Qiita APIのアクセストークン取得**
Qiitaの設定画面からQiitaAPIのアクセストークンを発行する．

![スクリーンショット 2024-12-01 23.19.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/1ea8d241-13fe-e853-858a-e567fae14a71.png)

**2. AWS Lambdaの設定**
バックエンド(API叩くのみ)はPythonで実装します．

- Python 3.xのランタイムで新規Lambda関数を作成
![スクリーンショット 2024-12-01 23.21.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/7008a00e-d1a8-185a-d345-42e0bb39f1af.png)

> 「関数を作成」を押して一旦作成してください

- 環境変数`QIITA_ACCESS_TOKEN`に1で作成したQiitaのアクセストークンを設定
![スクリーンショット 2024-12-01 23.22.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/94f35b55-b60e-38d1-6eb5-69c6a6f733d5.png)

**3. ローカルでPythonプロジェクトを作成**
まずディレクトリを作成
```bash
mkdir lambda_function
cd lambda_function
```
pythonファイルを作成
```
touch lambda.py
```
以下のように実装．QiitaID(user_id)はフロントからクエリパラメータとして待ち受ける．
```lambda.py
import os
import json
import requests

def lambda_handler(event, context):
    try:
        # クエリパラメータからuser_idを取得
        user_id = event.get('queryStringParameters', {}).get('user_id')

        if not user_id or not isinstance(user_id, str):
            return {
                'statusCode': 400,
                'body': json.dumps({
                    'count': 0,
                    'message': 'user_id is required'
                })
            }

        qiita_access_token = os.environ.get('QIITA_ACCESS_TOKEN')
        if not qiita_access_token:
            raise Exception('QIITA_ACCESS_TOKEN is not configured')

        per_page = 100
        page = 1

        headers = {
            'Content-Type': 'application/json',
            'Authorization': f'Bearer {qiita_access_token}'
        }

        query_params = {
            'page': str(page),
            'per_page': str(per_page),
            'query': f'user:{user_id} created:>=2024-12-01 created:<=2024-12-15'
        }

        url = 'https://qiita.com/api/v2/items'
        response = requests.get(url, headers=headers, params=query_params)

        if not response.ok:
            error_text = response.text
            raise Exception(f'Failed to fetch data from Qiita API: {response.status_code} {error_text}')

        data = response.json()

        return {
            'statusCode': 200,
            'headers': {
                'Content-Type': 'application/json'
            },
            'body': json.dumps({
                'count': len(data),
                'articles': [
                    {
                        'id': article['id'],
                        'title': article['title'],
                        'userId': article['user']['id'],
                        'userName': article['user']['name'],
                        'created_at': article['created_at'],
                        'updated_at': article['updated_at'],
                        'url': article['url']
                    } for article in data
                ]
            })
        }

    except Exception as error:
        print('Error details:', error)
        return {
            'statusCode': 500,
            'body': json.dumps({
                'count': 0,
                'message': str(error) or 'Internal Server Error'
            })
        }
```

依存関係を定義するrequirements.txtを作成
```bash
touch requirements.txt
```
以下のように記述
```requirements.txt
requests==2.28.2
```

その後以下のコマンドでプロジェクトに依存関係をインストール
```bash
pip install --target ./packages -r requirements.txt
```

最後にプロジェクトごとzip化する．
```bash
zip -r ../zip_lambda_deploy .
```

**4. Lambdaにzipをアップロード**
![スクリーンショット 2024-12-01 23.29.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/c11aeb60-45e7-31f7-b3f1-b99a43653f1c.png)

作成した関数を選択して，「コード」→「アップロード元」→「.zipファイル」を選択し，3で作成したzipをアップロードする．

Lambdaはこれで自動で更新されるのでOK

**4. APIGatewayでLambda関数を待ち受ける**
APIGatewayのホームから「APIを作成」を選択し，「HTTP API」を「構築」する．

![スクリーンショット 2024-12-01 23.33.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/2bbc245a-9e8e-bb93-b511-0c821b444604.png)

色々設定があるが特に何も設定しなくてもOK．API名のみ何か考える．
![スクリーンショット 2024-12-01 23.34.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/d481fae3-9753-f44d-a42f-eb0e013ddd44.png)

Lambda関数にアクセスするためのエンドポイントを指定するため，まず「ルート」で「作成」を選択
![スクリーンショット 2024-12-01 23.36.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/a2bed46a-d039-891c-be30-cffbcf89d2ee.png)

GETやPOSTやANYといったメソッドで設定するエンドポイントで使用すものを選択し，エンドポイント名を入力する．
![スクリーンショット 2024-12-01 23.36.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/9f9f2182-8e16-c5b7-c4a2-a7647c7e2601.png)
エンドポイントを作成したらLambda関数を統合するために「統合をアタッチする」を選択する．
![スクリーンショット 2024-12-01 23.37.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/c29e8052-3b75-89a5-60ba-db002d6de962.png)
統合タイプを「Lambda関数」にしてリージョンと統合するLambda関数を選択する．
![スクリーンショット 2024-12-01 23.38.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/e694218a-a9a6-df79-3550-6ca248eb423a.png)
最後にCORSを設定する．「Access-Control-Access-Origin」を選択し，フロントエンドのURLを入力する．

> 一旦localhostや*にしてもいいがセキュリティ上後で正しいフロントURLを入力しよう．

![スクリーンショット 2024-12-01 23.38.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/53f069d4-9633-ac11-c8da-d18e782ebed0.png)

これでOK！APIGatewayは何も設定していなければ自動デプロイできる．

# 作成したAPIのリンクを確認
作成したLambda関数を見るとAPIGatewayが存在するのでAPIGatewayをクリックするとAPIエンドポイントがいる．これをフロントで使用すればOK
![スクリーンショット 2024-12-01 23.44.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/e9ef52de-6e05-24df-c83e-4a7d87211045.png)


# フロントでどう使うのか
こんな感じでフロントで使えばいい．フロントの詳細はPart2(2024/12/25)で語ります．
```tsx
const fetchArticleCount = async () => {
    try {
      setLoading(true);
      const response = await fetch(`https://APIGatewayのAPIエンドポイント?user_id=${encodeURIComponent(userId)}`);
      const data = await response.json();
    } catch (error) {
      console.error(error);
    } finally {
    // ...
    }
  };
```

今回は簡単なAPI作成をLambdaとAPIGatewayでデプロイしました．フロントエンドエンジニアの方も簡単なAPIを使いたいことがあると思うのでぜひAWSを使って簡単なAPIをデプロイしてフロントエンドの開発に活用しましょう！

今回作成したアプリはこちらから．ぜひ使ってみてください！
[Qiita AdventCalender 2024 ChristmasTree](https://manji-qiita-adcal.click/)

それでは今日のクリスマスツリーです．
![スクリーンショット 2024-12-01 22.28.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/e8d29436-f6c4-62c1-2e29-9b4a1317f188.png)

Part2
https://qiita.com/JavaLangRuntimeException/items/7336877a892ac939d0fe