Simple Serverless FastAPI with AWS Lambda

Learn how to create a simple Serverless FastAPI with AWS Lambda and API Gateway.

The Serverless FastAPI will be ran on an AWS Lambda by using Mangum and AWS API Gateway will handle routing all requests to the Lambda.

You can find the repository for the finished app here.

I originally came across Mangum through this awesome blog post here that shows an example of Continuously Deploying a FastAPI to AWS Lambda with SAM CLI and Travis.

About AWS Lambda and API Gateway

Traditionally a Rest API would be hosted on an EC2 instance or something similar. This requires a standing server to be provisioned constantly even when it is not being used potentially incurring more cost.

Another option is to setup API Gateway to use a wildcard for any endpoint and redirect to the FastAPI Lambda and internally route the request accordingly. This way the only cost incurred is whenever there is a request made to the API.

Caveats

Lambdas can be a slightly slower to respond since they have to be spun up whenever there is a request.

However, AWS does a pretty good job of reusing Lambdas for following requests after the initial one in order to reduce latency.

Depending on your situation a standing server might have less latency. So if optimizing speed is your highest priority then that's something you might want to take into account.

Requirements

  • Python
  • Virtualenv
  • AWS Account

Create FastAPI App

Virtualenv

First you need to create and activate your virtual environment.

There are several different tools you can use for this so feel free to use your desired method. In this tutorial I'm going to use Virtualenv.

If you're not familiar with how to do this you can follow this quick tutorial on How To Install Virtualenv on Mac OS.

Setup Virtual Environment

Once you have Virtualenv installed create a new folder.

mkdir serverless-fastapi

Then cd into it.

cd serverless-fastapi

Running the following command will create a new folder in the root of your project where all of the dependencies of the app will be stored.

virtualenv -p python3.7 env

Then set your terminal to reference the virtual environment.

source ./env/bin/activate

Install FastAPI

Now that the virtual environment is setup we can install some dependencies which will be FastAPI and Uvicorn

pip install fastapi[all] uvicorn

Add main.py File

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
  return {"message": "Hello World"}

Run FastAPI

uvicorn main:app --reload

In your browser visit htpp://localhost:8000/docs

Here you can test your endpoint by clicking on the GET / Root route and then clicking execute.

Add Another Route

Inside the main.py file we're going to add a users route. So your main.py should look like the following.

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}

@app.get("/users")
async def get_users():
    return {"message": "Get Users!"}

Check the swagger docs again and you should see your new Users route.

Restructure FastAPI Routing

Right now our file tree should look something like this.

.
├── env
├── main.py

This is fine for smaller applications but as the API grows and we add more endpoints and want to handle more business logic we will need a better way to organize our routes.

We will be changing our file tree to something like this.

.
└── app
    ├── __init__.py
    ├── main.py
    └── api
        ├── __init__.py
        └── api_v1
            ├── __init__.py
            ├── api.py
            └── endpoints
                ├── __init__.py
                └── users.py
  1. Create app folder
  2. Move main.py into the app directory
  3. Create __init__.py file inside the app folder
  4. Create the api directory and subdirectory api_v1
  5. Add another __init__.py file inside both of those.
  6. Create the endpoints directory and you guessed it another __init__.py inside that one.
The __init__.py files are what tells Python to treat these as a module that it can import from any of the other files in the project. You'll see how this works in a bit.

Create users.py

Create another file in the endpoints directory called users.py and add the following snippet.

from fastapi import APIRouter

router = APIRouter()

@router.get("/")
async def get_users():
    return {"message": "Users!"}

Here we copied over our users route and changed it from @app.get to @router.get. You'll also notice we removed the users from the route itself and only have "/" You'll see why we did that next.

Create api.py

Create another file in the api_v1 folder called api.py and add the following snippet.

from fastapi import APIRouter

from .endpoints import users

router = APIRouter()
router.include_router(users.router, prefix="/users", tags=["Users"])

This is where we can add any new endpoints we want to keep separated and add the prefix "/users" on all sub routes for the users endpoint.

For example if we wanted to create another route to manage blog posts we could add the following.

from fastapi import APIRouter

from .endpoints import users

router = APIRouter()
router.include_router(users.router, prefix="/users", tags=["Users"])
router.include_router(users.router, prefix="/posts", tags=["Posts"])

The tags argument we are passing into the router.include_router adds a tag to the swagger documentation which you will see in a moment.

Update main.py

Now we need to update the main.py file to include our new Users router.

from fastapi import FastAPI
from app.api.api_v1.api import router as api_router

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}


app.include_router(api_router, prefix="/api/v1")

Serverless FastAPI with AWS Lambda

Mangum allows us to wrap the API with a handler that we will package and deploy as a Lambda function in AWS. Then using AWS API Gateway we will route all incoming requests to invoke the lambda and handle the routing internally within our application.

Install Mangum

Get the PyPi package here

pip install mangum

Update main.py

Now that we have Mangum installed we need to implement it within our main.py file.

from fastapi import FastAPI

from app.api.api_v1.api import router as api_router
from mangum import Mangum

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World!"}


app.include_router(api_router, prefix="api/v1")
handler = Mangum(app)

Setup AWS Resources

This tutorial should fall under the AWS free-tier to create, however continued usage of these resources will have associated costs.

Create S3 Bucket

  • Navigate to S3 in the AWS console and click Create Bucket.
  • Give it a name and click Create. In this example we're going to call it serverless-fastapi-lambda-dev

Upload Zip File

Before we can create the lambda we need to package up our existing FastApi app so its ready for AWS Lambda.

The lambda won't be running in the virtual environment and will only be running one command (which we will determine when we create it) which will be app.main.handler So we need to install all the dependencies within the zip file since it won't be running pip install like we did locally.

Package Lambda

Inside your terminal from the root directory of your project, CD into the Python Site Packages folder.

cd env/lib/python3.7/site-packages

Then zip up the contents into the root of the project.

zip -r9 path/to/root/of/project/function.zip

CD back into the root of the project.

cd path/to/root/of/project

Next we need to add the contents of the app folder so let's add that into the zip file.

zip -g ./function.zip -r app

Upload Zip File to S3

  • Go inside the S3 bucket you just created and click Upload.
  • Add your zip file through the interface that pops up and and click Upload.

Create AWS Lambda

  • Navigate to Lambda and click Create Lambda
  • Author from scratch and add a name
  • Select Python 3.7 Runtime
  • Choose or create an execution role
  • Create new role using the default is fine.

Next we need to upload the zip file from S3. For this we will need the path to the zip file in our S3 bucket. So go to your zip file and check the tick box next to it and click copy path.


Now back in the Lambda dashboard for your function click the Actions dropdown and select Upload a file from Amazon S3.

Update Handler

By default Lambdas will look for the handler to call the function at lambda_function.lambda_handler So we need to change this to match FastAPI.

Under the Basic Settings menu click Edit then update the Handler to app.main.handler

Test FastAPI Lambda

Click the Test button at the top of the dashboard and the select the Event Template dropdown and search for API Gateway Proxy.

This test is going to simulate API Gateway invoking the Lambda on a request to our API. So in the JSON document provided we need to update the path and httpMethod key value pairs from the following:

{
  "body": "eyJ0ZXN0IjoiYm9keSJ9",
  "resource": "/{proxy+}",
  "path": "/path/to/resource",
  "httpMethod": "POST",
  "isBase64Encoded": true
  ...
}

To this

{
  "body": "eyJ0ZXN0IjoiYm9keSJ9",
  "resource": "/{proxy+}",
  "path": "/",
  "httpMethod": "GET",
  "isBase64Encoded": true
  ...
}

Once you have updated the JSON file go ahead and click Create then click the Test button again.

Create API Gateway

Now we need to choose which kind of API Gateway we want to build. In this case we are going to make a public REST API. So navigate to API Gateway and then click the Build button under the REST API option that DOES NOT say "private".

Choose the Protocol

Keep all the default configurations on this page as is. Then give your API Gateway a name and an optional description then click Create API

Now your API Gateway should look something like this.

Create Root Proxy Method

Next the Root Proxy Method will handle forwarding all requests with the base path of / to our FastAPI Lambda. Let's go ahead and create this Method.

  1. Navigate to API Gateway and Click on the Resources menu item in the left-hand sidebar.
  2. Select Create Method in the Actions dropdown
  3. Select ANY from the prompted dropdown and then click the check mark.
  4. Check use Lambda Proxy Integration and enter the Lambda Function name than click Save.
  5. It will prompt you to add permission to Lambda function click OK.

Create Resource

Next the following Resource or endpoint we are need to make will handle all other requests.

So if you add a new route to FastAPI like /posts API Gateway will forward the request to the Serverless FastAPI Lambda the same as if it was / or any other path /pets/{pet_id}

  1. Click on resources and select Create Resource from the Actions dropdown
  2. Check Configure as proxy
  3. This should automatically add the /{proxy+} to the Resource Path but if not make sure you add it in there.
  4. Add Lambda Function Name and leave everything else as default and click Save
  5. It will prompt you to add permission to Lambda function click OK.

Deploy Lambda Proxy API

Finally we need to deploy the API in order for the endpoint to be public.

  1. Select Deploy API from the Actions dropdown
  2. Select New Stage from Deployment Stage dropdown
  3. Name it and click Deploy

You will then be taken to the Stage dashboard and you can access your newly deployed Serverless FastAPI with AWS Lambda by clicking the **Invoke URL** link! It should look something like this.

Invoke URL: https://a7kxkebqij.execute-api.us-east-1.amazonaws.com/dev

Summary

That's it! We Created a FastAPI application and converted it to an AWS Lambda. Then we setup API Gateway to route all requests to our Lambda proxy.

If you want to see how to setup a CICD pipeline for this app using CircleCi and Github you can check out Serverless FastAPI CICD with CircleCI

Serverless FastAPI with AWS Lambda Video Walkthrough


You can find the video for this and other Web Development tutorials on the Deadbear Youtube Channel DeadbearCode!