AWS SAM CLIを用いてREST APIを作成してみた

  •  
 
トビウオ2024年3月4日 - 12:11 に投稿

概要

AWS SAM (AWS Serverless Application Model) とは、サーバーを意識せずに構築する Web API ……いわゆる「サーバーレスアプリケーション」を簡単に作成するためのツールキットです。

公式ドキュメントにおけるサンプルでは、 Amazon API Gateway を介して AWS Lambda function をキックし、 AWS Lambda function は Amazon DynamoDB と連携……といった構成も簡単に書けることが示されています。

サンプル画像

理屈としては、 AWS SAM CLI を用いて作成したプロジェクトを編集・実行することで、「どういったクラウドリソースが必要か?」を AWS CloudFormation の形式で書き下し、それをデプロイすることで、クラウドリソースが AWS 上に作られる感じ。つまり、既存の手段に加えて新たに、「AWS SAM を使う」というお手軽メソッドも増えたわけですね。

  • AWS 上で REST API を構築する手段
    • AWS の Web フォーム上でリソースを作る
    • AWS CloudFormation (CFn) のテンプレートを手書きする
    • AWS Cloud Development Kit (CDK) でプログラムを書く
    • AWS SAM を使う ← New!

作り方

丁寧な説明は公式のチュートリアルに譲るとして、ここではざっくりとした手順を示します。

  1. AWS SAM CLI をインストール
  2. sam init コマンドでプロジェクトを作成
    • 対話形式でテンプレートやオプションを選択する
    • 以下の記述では、「AWS Quick Start Templates」の「Hello World Example TypeScript」を「nodejs20.x」で動作させ、デプロイ手段に「Zip」を選択したものとする
  3. プロジェクトのディレクトリに移動し、 sam build でビルド
    • 事前にnpm iなどとして必要なパッケージを導入しておくこと
  4. 開発
    • sam local invokeで Lambda function の動作を確認
      • GETパラメーターなどは、sam local invoke -e event.jsonとJSON形式で渡す
    • sam local start-apiで REST API の動作を確認
    • sam sync --watchで差分を自動デプロイ
  5. sam deploy でデプロイ
    • デプロイする際は、デプロイ先のリージョンを--regionオプションで指定するか、環境変数としてAWS_DEFAULT_REGIONを設定しておく
  6. sam delete で削除

今回作成するREST API

手順1:プロジェクトの作成

  • プロジェクト名はaozora-search-appとした
  • まずtemplate.yamlを以下のように編集する
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /api/aozora-books
            Method: get
Outputs:
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/api/aozora-books"
  • 次に、app.tsを以下のように編集する
    • エラーチェックは省略している
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
  // GETパラメーターを取得
  const title = event.queryStringParameters?.title;
  const author = event.queryStringParameters?.author;
  const maxResults = event.queryStringParameters?.maxResults;

  return {
    statusCode: 200,
    body: JSON.stringify({
      title,
      author,
      maxResults,
    }),
  };
};

手順2:デプロイ含めた動作確認

  • 確認のためにevent.jsonを作成し、sam local invoke -e event.jsonで動作確認する
{
  "queryStringParameters": {
    "title": "こころ",
    "author": "夏目漱石",
    "maxResults": "10"
  }
}
  • sam local start-apiでローカル上のWeb APIを立ち上げ、curlコマンドで動作確認する
    • 例:http://127.0.0.1:3000/api/aozora-books/?title=%E3%81%93%E3%81%93%E3%82%8D&author=%E5%A4%8F%E7%9B%AE%E6%BC%B1%E7%9F%B3&maxResults=10
> % curl http://127.0.0.1:3000/api/aozora-books/\?title\=%E3%81%93%E3%81%93%E3%82%8D\&author\=%E5%A4%8F%E7%9B%AE%E6%BC%B1%E7%9F%B3\&maxResults\=10 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    63  100    63    0     0   1967      0 --:--:-- --:--:-- --:--:--  2032
{
  "title": "こころ",
  "author": "夏目漱石",
  "maxResults": "10"
}
  • sam deploy --guidedでデプロイする。例えば次のような設定になる
        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [aozora-search-app]: 
        AWS Region [us-east-1]: ap-northeast-1
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [Y/n]: y
        #SAM needs permission to be able to create roles to connect to the resources in your template
        Allow SAM CLI IAM role creation [Y/n]: y
        #Preserves the state of previously provisioned resources when an operation fails
        Disable rollback [y/N]: 
        HelloWorldFunction has no authentication. Is this okay? [y/N]: y
        Save arguments to configuration file [Y/n]: 
        SAM configuration file [samconfig.toml]: 
        SAM configuration environment [default]: 
  • デプロイ後は、Webブラウザーなどから動作を確認できる
    • 例:https://example.execute-api.ap-northeast-1.amazonaws.com/Prod/api/aozora-books/?title=%E3%81%93%E3%81%93%E3%82%8D&author=%E5%A4%8F%E7%9B%AE%E6%BC%B1%E7%9F%B3&maxResults=10

手順3:本格的に実装

  • エラーチェックなど細かな実装は都度行うこと
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
const { XMLParser } = require('fast-xml-parser');
export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
  // GETパラメーターとして、title・author・maxResultsを受け取る
  const title = event.queryStringParameters?.title;
  const author = event.queryStringParameters?.author;
  const maxResults = event.queryStringParameters?.maxResults;

  // 入力バリデーション
  if (!title && !author) {
    return {
      statusCode: 400,
      body: JSON.stringify({
        message: 'title or author is required',
      }),
    };
  }

  // APIにアクセスし、結果を取得するためのURLを作成
  const url = 'https://ndlsearch.ndl.go.jp/api/sru';
  const params = new URLSearchParams();
  params.append('operation', 'searchRetrieve');
  if (maxResults) {
    params.append('maximumRecords', maxResults);
  }
  let query = `dpid="aozora"`;
  if (title) {
    query += ` AND title="${title}"`;
  }
  if (author) {
    query += ` AND creator="${author}"`;
  }
  params.append('query', query);
  console.log('URL', `${url}?${params.toString()}`);

  // APIにアクセスし、結果を取得
  const response = await fetch(`${url}?${params.toString()}`);

  // 結果を変換して返す
  const xmlData = await response.text();
  const parser = new XMLParser();
  const javaScriptData = parser.parse(xmlData);
  const bookRecords = javaScriptData.searchRetrieveResponse.records.record.map((item: any) => {
    const itemData = parser.parse(item.recordData);
    return {
      'title': itemData['srw_dc:dc']['dc:title'],
      'author': itemData['srw_dc:dc']['dc:creator'],
      'publisher': itemData['srw_dc:dc']['dc:publisher'],
      'description': itemData['srw_dc:dc']['dc:description'],
      'langugage': itemData['srw_dc:dc']['dc:language'],
    };
  });

  return {
    statusCode: 200,
    body: JSON.stringify(bookRecords)
  };
};
  • 以上により、例えばhttps://example.execute-api.ap-northeast-1.amazonaws.com/Prod/api/aozora-books?title=%E3%81%93%E3%81%93%E3%82%8D&author=%E5%A4%8F%E7%9B%AE%E6%BC%B1%E7%9F%B3&maxResults=10にアクセスすると、結果がJSON形式で返却される
[
    {
        "title": "『心』広告文",
        "author": "夏目 漱石 著者",
        "publisher": "岩波書店",
        "description": [
            "「時事新報」1914(大正3)年9月26日",
            "漱石全集 第十六巻",
            "岩波書店",
            "1995(平成7)年4月19日",
            "1995(平成7)年4月19日",
            "1995(平成7)年4月19日",
            "漱石全集 第十一巻",
            "岩波書店",
            "1966(昭和41)年10月",
            "新字旧仮名"
        ],
        "langugage": "jpn"
    },
    (以下略)

まとめ

  • AWS SAM CLIを用いて、外部APIにアクセスするようなREST APIを作成できた
  • キャッシュ機構の搭載、データベースとの連携などは今後の課題とする

コメントを追加

プレーンテキスト

  • HTMLタグは利用できません。
  • 行と段落は自動的に折り返されます。
  • ウェブページのアドレスとメールアドレスは自動的にリンクに変換されます。
CAPTCHA
この質問はあなたが人間の訪問者であるかどうかをテストし、自動化されたスパム送信を防ぐためのものです。