Serverless AWS Lambda ➕ DynamoDB ➕ FastAPI ➕ Github Actions

–––

Table of Contents 📚

  1. Introduction
  2. AWS Serverless Application Model (AWS SAM)
    1. Requirements
  3. Creating Function
  4. Adding Dependencies
  5. Writing APIs using FastAPI
  6. Creating Handler using Mangum
  7. Updating Cloudformation Template
  8. Build & Deploy
  9. Conclusion

Introduction

In the Part 1 of this blog, we will go through the steps of setting up AWS SAM, creating a Lambda, and adding FastAPI API endpoints.

You might have heard a lot about serverless functions and their advantages so in this blog we won't be going into the explanation of, what are serverless functions? or where and when to use them?.

In short, a serverless architecture or serverless function can be use when one just wants to focus on the application building part and doesn't want to get into the complexities of setting and configuring a server.

In more layman's terms, a serverless architecture can be compared to Service Apartments/Paying Guests in the real world. When people want a place which is safe, has all the basic necessities, has multiple accomodation options, and provides freedom to add some customisations they look for Service Apartments/Paying Guests. Similarly, in case of application development, if you don't want to deal with setting up servers, securing and configuring them, then serverless is the best option.

AWS Serverless Application Model (AWS SAM)

There are many ways you can create a serverless Lambda function on AWS. A Docker container with an lambda_handler pushed to AWS ECR can be used with Lambda.

Or, zipping the requirements and uploading those to AWS S3 and then building a Lambda function via the console can also work.

But AWS SAM is the best approach to have continuous integration and deployment. AWS SAM provides template specification to define serverless application and a CLI (command line interface) tool to build and deploy.

Moreover, we can integrate Github Actions using with AWS SAM and have a continuous development and deployment pipeline

Requirements

To follow along with this blog you need to install and configure the following

Creating Function

  1. Open a new VS Code editor window and based on your operating system use cmd + shift + p or ctrl + shift + p and search for "AWS: Create Lambda SAM Application" and select it
  1. Select Python3.7 or any python version you want
  1. Select AWS SAM Hello World as the application template
  1. Name your application and save it inside a folder and export the folder to VS Code

Once the folder is added to the current workspace the structure will look similar to the one in the image below

Adding Dependencies

To create APIs or endpoints using FastAPI we first need to add the fastapi inside the requirements.txt file

application_name/hello_world/requirements.txt
requests
fastapi

Writing APIs using FastAPI

Let's create some endpoints. Open the app.py file and comment out the lambda_handler function and create some endpoints as below

application_name/hello_world/app.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World", "ok": True}

@app.get("/hello")
def hello_mount():
    return {"message": "All is well", "ok": True}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q, "ok": True}

Creating Handler using Mangum

Now to handle the function invocation or various events via the API Gateway or the Load Balancer we use Mangum.

Mangum supports ASGI application frameworks like Starlette, FastAPI, and Quart. To know more about Mangum you can visit this page

Add mangum to our requirements.txt

application_name/hello_world/requirements.txt
requests
fastapi
mangum

Now let's create the lambda_handler inside the app.py file

application_name/hello_world/app.py
from fastapi import FastAPI
from mangum import Mangum

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World", "ok": True}

@app.get("/hello")
def hello_mount():
    return {"message": "All is well", "ok": True}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q, "ok": True}


lambda_handler = Mangum(app = app, lifespan="off")

Updating Cloudformation Template

We need to allow access to all the endpoints we have created above or else we might get 500 error code in response.

To do this add open the template.yaml file and replace the content under Events with the following

Events:
  HelloWorld:
    Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
    Properties:
    Path: /{proxy+}
    Method: any
  Http:
    Type: Api
    Properties:
    Path: "/"
    Method: Any

After this update, the template file should look something like below

application_name/template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
  hello-world-lambda-python3.7

  Sample SAM Template for hello-world-lambda-python3.7

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /{proxy+}
            Method: any
        Http:
          Type: Api
          Properties:
            Path: "/"
            Method: any

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

Build & Deploy

Let's build the function and deploy it. To build the function open the terminal and move into the application_name.

Note: application_name refers to the name of the application you gave at the start.

Run the following commands

  1. Build the application
sam build

Once the build process is complete you will see .aws-sam folder inside your function. This function contains all the packages and files which will be required to run the application

  1. Deploy the Application
sam deploy --region <region_name> \
           --stack-name <name-the-deployment-stack> \
           --resolve-s3 --capabilities CAPABILITY_IAM

Let's say we want to deploy it to the us-east-2 region and we name the cloudformation stack as lambda-fastapi-deploy our command will be

sam deploy --region us-east-2 \
           --stack-name lambda-fastapi-deploy \
           --resolve-s3 --capabilities CAPABILITY_IAM

Once Cloudformation finishes deploying the application to Lambda it will output the endpoint to access all the APIs in the terminal window.

Conclusion

This blog was more of a discovery and setup kind wherein we went through the process of creating APIs using FastAPI and wrapping it into an adapter called Mangum and then deploying it as a Lambda using the Cloudformation template.

In the next part, we will create a few tables inside DynamoDB and try to access them using the Lambda function we created here