Deploy FastAPI on AWS Lambda using AWS SAM

So you have this FastAPI project, there aren’t any strings attached to it, you could very well package it inside a docker container and serve it on ECS or EC2. But if your API is a good fit for Lambda, I would argue that you should simply throw it there and forget about it. I’ll walk you through how to deploy an already developed API in AWS Lambda, without performing deep changes to your code logic.

Minimal changes in code logic required

There are several ways to architect your endpoints if you want to serve them using Lambda and API Gateway. You can read the official AWS word on this matter. In this post, I argue that if your use-case is simple enough you should go with the ‘Monolithic Lambda function’ approach.

This will allow you to:

  1. Have a collection of endpoints gathered in a single service
  2. Deploy your existing API with minimal changes to your code logic
  3. Maintain your current development/testing strategy

Demo application structure

demo
├── app
│   ├── app.py
│   ├── __init__.py
│   └── requirements.txt
├── README.md
├── template.yaml
└── tests
    ├── __init__.py
    ├── requirements.txt
    └── test_handlers.py

The example code for this post is available on Github. We have a simple FastAPI service, a collection of 3 endpoints, and some tests. I’m going for python 3.8, so the local setup goes like this:

# Setup your local env
virtualenv -p python3.8 -v venv
source venv/bin/activate
pip install -r tests/requirements.txt -r app/requirements.txt

# Run the tests
pytest -v

# Serve the API locally using an uvicorn worker
uvicorn app.app:app --reload

As you can see, at this point our demo service is generic, its structure is not tied up with any sort of deployment strategy. It could be deployed on bare-metal, any container-orchestrator, or on lambda with minimal changes needed.

Adding the only Lambda specific change to our code

You’ll see that a handler is being added to the app.py file.

handler = Mangum(app)

Mangum is just an adapter for ASGI (Asynchronous Server Gateway Interface) applications, in which FastAPI is included, that allows them to run on Lambda/API Gateway. This is the only code-specific change required for our service to be served by AWS Lambda. Once you have this setup, you can edit your template.yaml Properties key.

Resources:
  AppFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: app/
      Handler: app.handler
      Runtime: python3.8
      Events:
        Root:
          Type: Api
          Properties:
            Path: /
            Method: ANY
        NonRoot:
          Type: Api
          Properties:
            Path: /{proxy+}
            Method: ANY

As you can see, we are instructing SAM to look for the code to deploy in the app/ folder and we are pointing the proper Mangum handler app.handler. On the Events tab we are creating two routes:

  1. Root - for the root endpoint
  2. NonRoot - that encapsulates all endpoints other than root.

Both Events accept ANY HTTP methods (PUT, POST, etc). API Gateway will now route every HTTP request, regardless of it’s HTTP verb or path to our FastAPI service.

Deploy

You are ready to go!

# to deploy your API run - fill-up the prompt that appears with the AWS region that you want to use, etc.
sam build --use-container
sam deploy --guided

# in case you want to take down the hole cloudformation stack generated by SAM
aws cloudformation delete-stack --stack-name <stack-name>

SAM should print out your recently created API Gateway endpoint, which you should use to trigger your endpoints.

curl -v https://g1g234234fsfg1k49.execute-api.us-east-1.amazonaws.com/Prod/
curl -v https://g1g234234fsfg1k49.execute-api.us-east-1.amazonaws.com/Prod/ping
curl -v https://g1g234234fsfg1k49.execute-api.us-east-1.amazonaws.com/Prod/ping/ping

Voilà.

· python, fastapi, lambda, aws, sam, serverless