一覧に戻る
Serverless

標準サーバーレス API アーキテクチャ

API Gateway と AWS Lambda を連携させた、標準的な RESTful または HTTP API エンドポイントを瞬時にプロビジョニングする構成です。

構成要素 (AWS Services):

API GatewayLambdaIAM

構築される主要リソース (11種):

IAM Role2
ApiGateway Method2
DynamoDB Table1
Lambda Function1
Logs LogGroup1
ApiGateway RestApi1
ApiGateway Resource1
ApiGateway Deployment1
ApiGateway Stage1
ApiGateway Account1
Lambda Permission1

アーキテクチャ図 (Architecture Diagram)

クリックで拡大表示
標準サーバーレス API アーキテクチャ アーキテクチャ図

AWS CLI でのデプロイ例

AWS CLIを使用してCloudFormationスタックをデプロイする場合は、以下のコマンドを実行します。

aws cloudformation deploy \
  --template-file serverless-api.yaml \
  --stack-name serverless-api-stack \
  --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM
serverless-api.yaml
DL
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Serverless API with Lambda, API Gateway, and DynamoDB - Best Practices'

Resources:
  ApiTable:
    Type: AWS::DynamoDB::Table
    Properties:
      BillingMode: PAY_PER_REQUEST
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      SSESpecification:
        SSEEnabled: true
      StreamSpecification:
        StreamViewType: NEW_AND_OLD_IMAGES
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH

  ApiFunction:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.13
      Handler: index.handler
      Architectures:
        - arm64
      Environment:
        Variables:
          TABLE_NAME: !Ref ApiTable
      Code:
        ZipFile: |
          import json
          import os
          import boto3
          from datetime import datetime, timezone
          
          dynamodb = boto3.resource('dynamodb')
          table = dynamodb.Table(os.environ['TABLE_NAME'])
          
          def handler(event, context):
              try:
                  method = event['httpMethod']
                  
                  if method == 'GET':
                      response = table.scan()
                      return {
                          'statusCode': 200,
                          'headers': {'Content-Type': 'application/json'},
                          'body': json.dumps(response['Items'])
                      }
                  
                  elif method == 'POST':
                      body = json.loads(event['body'])
                      item = {
                          'id': body['id'],
                          'data': body.get('data', ''),
                          'timestamp': datetime.now(timezone.utc).isoformat()
                      }
                      table.put_item(Item=item)
                      return {
                          'statusCode': 201,
                          'headers': {'Content-Type': 'application/json'},
                          'body': json.dumps(item)
                      }
                  
                  return {
                      'statusCode': 405,
                      'body': json.dumps({'error': 'Method not allowed'})
                  }
              
              except Exception as e:
                  return {
                      'statusCode': 500,
                      'body': json.dumps({'error': str(e)})
                  }
      Role: !GetAtt ApiFunctionRole.Arn
      ReservedConcurrentExecutions: 10
      LoggingConfig:
        LogGroup: !Ref ApiFunctionLogGroup

  ApiFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: DynamoDBAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:GetItem
                  - dynamodb:PutItem
                  - dynamodb:Scan
                  - dynamodb:Query
                Resource: !GetAtt ApiTable.Arn

  ApiFunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      RetentionInDays: 7

  RestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: !Sub ${AWS::StackName}-api
      EndpointConfiguration:
        Types:
          - REGIONAL

  ApiResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref RestApi
      ParentId: !GetAtt RestApi.RootResourceId
      PathPart: items

  ApiMethodGet:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref RestApi
      ResourceId: !Ref ApiResource
      HttpMethod: GET
      AuthorizationType: NONE
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiFunction.Arn}/invocations

  ApiMethodPost:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref RestApi
      ResourceId: !Ref ApiResource
      HttpMethod: POST
      AuthorizationType: NONE
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiFunction.Arn}/invocations

  ApiDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn:
      - ApiMethodGet
      - ApiMethodPost
    Properties:
      RestApiId: !Ref RestApi

  ApiStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      RestApiId: !Ref RestApi
      DeploymentId: !Ref ApiDeployment
      StageName: prod
      TracingEnabled: true
      MethodSettings:
        - ResourcePath: /*
          HttpMethod: '*'
          LoggingLevel: INFO
          DataTraceEnabled: true
          MetricsEnabled: true

  # NOTE: AWS::ApiGateway::Account represents the APIGateway global account settings per region.
  # If you already have active APIGateway CloudWatch roles set up in this region, this resource might fail deployment.
  # You can safely comment out or omit this resource in that case.
  ApiGatewayAccount:
    Type: AWS::ApiGateway::Account
    Properties:
      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchRole.Arn

  ApiGatewayCloudWatchRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: apigateway.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs

  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref ApiFunction
      Action: lambda:InvokeFunction
      Principal: apigateway.amazonaws.com
      SourceArn: !Sub arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*

Outputs:
  ApiUrl:
    Value: !Sub https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/prod/items
    Description: API endpoint URL
  TableName:
    Value: !Ref ApiTable
    Description: DynamoDB table name
  FunctionName:
    Value: !Ref ApiFunction
    Description: Lambda function name