Let's build an API that fetches DynamoDB data with AWS SAM
In short, from a table like this ...
| group(Hash) | name(Range) | 
|---|---|
| group1 | name1 | 
| group1 | name2 | 
| group2 | name3 | 
I want to take it like this.
curl https://hogehoge/Prod/dynamo-api/v1/?group=group1&name=name1
{
    "result": [
        {
            "name": "group1",
            "group": "name1"
        }
    ]
}
For construction, refer to the previous [AWS SAM] Introduction to Python version. (This is the procedure for initializing an AWS SAM application using Pipenv.)
The resources to be created this time are as follows
Now, let's write template.yaml to create the above resource.
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  dynamo-api
  Sample SAM Template for dynamo-api
#Lambda timeout settings
Globals:
  Function:
    Timeout: 60
#Definition of table name, function name, etc.
Parameters:
  DynamoAPIFunctionName:
    Type: String
    Default: dynamo-api
  DynamoTableName:
    Type: String
    Default: DynamoApiTable
#Resource definition
Resources
  DynamoTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Ref DynamoTableName
      #Hash key and Range key definitions (column name and type)
      AttributeDefinitions:
        - AttributeName: group
          AttributeType: S
        - AttributeName: name
          AttributeType: S
      KeySchema:
        - AttributeName: group
          KeyType: HASH
        - AttributeName: name
          KeyType: RANGE
      #Charge mode designation (PAY_PER_REQUEST is pay-as-you-go)
      BillingMode: PAY_PER_REQUEST
  DynamoAPIFunctionRole:
    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/CloudWatchLogsFullAccess'
      #Permission to read DynamoDB table (this allows you to only read the table you are creating this time)
      Policies:
        - PolicyName: 'DynamoApiTablePolicy'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:Get*
                  - dynamodb:Query
                Resource: !GetAtt DynamoTable.Arn
  DynamoAPIFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Ref DynamoAPIFunctionName
      Role: !GetAtt DynamoAPIFunctionRole.Arn
      # app.Directory path where py is
      CodeUri: dynamo-api/
      #Handler path (It's easy to make a mistake if you change it carelessly ... It's good to leave it alone)
      Handler: app.lambda_handler
      Runtime: python3.7
      #API Gateway settings (SAM-specific description)
      Events:
        DynamoApi:
          Type: Api
          Properties:
            Path: /dynamo-api/v1/
            Method: get
#The original role of the parameters specified in Outputs is to pass data to other templates.
#This time, it is used to output the API endpoint when the deployment is completed.
Outputs:
  DynamoApi:
    Description: "API Gateway endpoint URL for Prod stage for Dynamo API function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/dynamo-api/v1/"
The settings of Lambda and API Gateway are almost the same as the template, so you can write it easily.
First, install the required libraries.
$ pipenv install requests boto3
Next, we will create a process that actually fetches data from the Dynamo table.
Search the Dynamo table with the group and name specified in the GET parameter and return its value
It is a simple mechanism.
I tried to make name the front one.
app.py
import json
import json
import boto3
from boto3.dynamodb.conditions import Key
from http import HTTPStatus as status
DYNAMODB_TABLE_NAME = 'DynamoApiTable'
def lambda_handler(event, context):
    table = boto3.resource('dynamodb').Table(DYNAMODB_TABLE_NAME)
    params = event.get('queryStringParameters')
    results = dynamo_search(table, params)
    if results is None:
        return {
            "statusCode": status.BAD_REQUEST,
            "body": json.dumps({
                "error": "Bad Request"
            }, ensure_ascii=False),
        }
    return {
        "statusCode": status.OK,
        "body": json.dumps({
            "results": results
        }, ensure_ascii=False),
    }
def dynamo_search(table, params):
    if params.get('group'):
        keyConditionExpression = Key('group').eq(params.get('group'))
        if params.get('name'):
            keyConditionExpression &= Key('name').begins_with(params.get('name'))
        return table.query(
            KeyConditionExpression=keyConditionExpression).get('Items', [])
    return None
I want to convert from Pipfile to requirements.txt ...
In such a case, you can convert with this command.
(Boto3 is also prepared in Lambda from the beginning, so it may not be used much ..: thinking :)
$ pipenv lock -r > requirements.txt
This time, you can keep the default requirements.txt: sweat_smile:
$ sam build
Building resource 'DynamoAPIFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource
Build Succeeded
Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
If you have separate settings for ~ / .aws / credentials, add the --profile option.
The --guided option is OK only for the first time. (Because the settings are saved in samconfig.toml)
$ sam deploy --guided
Stack dynamo-api-stack outputs:
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
OutputKey-Description                                                                                 OutputValue                                                                                         
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
DynamoApi - API Gateway endpoint URL for Prod stage for Dynamo API function                           https://hogehoge/Prod/dynamo-api/v1/                     
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
The endpoint is also output properly ~
This time (also), I registered the recommended group name and name.
| group(Hash) | name(Range) | 
|---|---|
| Honey strap | Suou Patra | 
| Honey strap | Mico Sekishiro | 
| Hololive | Shirakami Fubuki | 
| ... | ... | 
Try sending a request to the output endpoint. It's hard to see, so let's format it with jq.
(People in the browser because curl is troublesome: information_desk_person :)
First, specify group (Hash key).
$ curl https://hogehoge/Prod/dynamo-api/v1/ \
--get \
--data-urlencode 'group=Honey strap' | jq
{
  "results": [
    {
      "name": "Suou Patra",
      "group": "Honey strap"
    },
    {
      "name": "Mico Sekishiro",
      "group": "Honey strap"
    },
    {
      "name": "Shimamura Charlotte",
      "group": "Honey strap"
    },
    {
      "name": "Mary Saionji",
      "group": "Honey strap"
    }
  ]
}
I got it: clap:
Of course, you can also search for name (Range key) with a prefix match.
$ curl https://hogehoge/Prod/dynamo-api/v1/ \
--get \
--data-urlencode 'group=Honey strap' \
--data-urlencode 'name=Suo' | jq
{
  "results": [
    {
      "name": "Suou Patra",
      "group": "Honey strap"
    }
  ]
}
I got it! It's perfect!
This time, I made a simple API. Next time, I will test with pytest.
Thank you for reading until the end!
[AWS SAM] Introduction to Python
Recommended Posts