Serverless FastAPI with AWS Lambda, API Gateway, and AWS CDK
By the end of this article, you’ll have a Python app deployed with FastAPI using AWS Lambda and API Gateway that is serving requests. In addition, you will have the infrastructure defined in code.
What is AWS Lambda and API Gateway
AWS Lambda and Amazon API Gateway are two services offered by AWS that enable developers to quickly spin up infrastructure without the hassle of provisioning or maintaining servers. In our case, we’ll host the FastAPI app in a Lambda function and the API Gateway will route requests to it.
This approach is immediately scalable upon deployment and includes a generous free tier by AWS.
How to deploy FastAPI with AWS Lambda + API Gateway + AWS CDK
Follow the instructions below to successfully deploy an AWS CDK app that includes a lambda function and API Gateway.
Create a requirements.txt
Create a requirements.txt
file with the following information:
1aws-cdk-lib==2.118.0
2aws-cdk.aws-lambda-python-alpha==2.118.0a0
3constructs>=10.0.0,<11.0.0
4fastapi==0.108.0
5uvicorn==0.25.0
6mangum==0.17.0
To serve FastAPI locally, I chose uvicorn
. This is not mandatory if you do not care about testing locally. To adapt the FastAPI router for API Gateway, we will use mangum
.
Install the dependencies
1pip3 install -r requirements.txt
Create /app folder with a main.py file
You can use your preferred file structure. Whether you use this:
1project/
2├── app
3│ ├── __init__.py
4│ ├── main.py
5│ ├── dependencies.py
6│ └── routers
7│ │ ├── __init__.py
8│ │ ├── items.py
9│ │ └── users.py
10│ └── internal
11│ ├── __init__.py
12│ └── admin.py
or this
1project/
2├── app
3│ ├── __init__.py
4│ └── main.py
ensure that there’s a main.py
at the root of the app
folder.
For the purposes of this tutorial, we’ll use a app/main.py
file to keep it simple for a small todo app:
1from fastapi import FastAPI
2from mangum import Mangum
3
4app = FastAPI()
5
6
7@app.get("/tasks")
8async def get_tasks():
9 # database lookup goes here
10 return [{"id": "ptvWZ3", "text": "hello!"}, {"id": "cqDUr3", "text": "another!"}]
11
12
13@app.get("/")
14async def root():
15 return {"message": "Hello World!"}
16
17
18handler = Mangum(app, lifespan="off")
Create a stack.py with an API Gateway and a lambda router
1from os import path
2
3import aws_cdk as cdk
4import aws_cdk.aws_lambda as lambda_
5import aws_cdk.aws_apigateway as apigateway
6import aws_cdk.aws_lambda_python_alpha as python
7
8
9class ApiStack(cdk.Stack):
10 def __init__(self, scope: cdk.App, construct_id: str, **kwargs) -> None:
11 super().__init__(scope, construct_id, **kwargs)
12
13 lambda_function = python.PythonFunction(
14 self,
15 "Function",
16 entry=path.join(path.dirname(__file__), "app"),
17 runtime=lambda_.Runtime.PYTHON_3_11,
18 index="main.py",
19 )
20
21 api = apigateway.LambdaRestApi(self, "API", handler=lambda_function)
NOTE: We are using a construct that is in alpha. This enables the AWS CDK to use Docker to bundle our Python requirements together.
This seems small, but we are leveraging the power of the AWS CDK and their L3 constructs. This CDK app will:
- Bundle python requirements and create a .zip file
- Create a lambda function with the handler as
main.handler
- Create an API Gateway
- Add a resource path of
{proxy+}
to the API - Add an
ANY
method to the resource path that points to the lambda function - Enable a “proxy integration” for the lambda function
What is an API Gateway proxy integration?
For the FastAPI logic to work appropriately, we need to configure the API Gateway to forward full API requests to the lambda function. With this setting, the lambda function’s responsibility increases as it must fulfill the requirements of the API Gateway service.
Fortunately, mangum
takes care of this.
Create a requirements.txt file for the /app folder
As of time of writing (Jan. 7th, 2024), the python alpha function entry
folder must have the requirements.txt
file in it. Create app/requirements.txt
file with the following:
1fastapi==0.108.0
2mangum==0.17.0
Create infra.py
Create a file that provisions the CDK app. I named mine infra.py
.
1import aws_cdk as cdk
2
3from stack import ApiStack
4
5app = cdk.App()
6ApiStack(app, "ApiStack")
7
8app.synth()
Create cdk.json
Ensure that the entry file matches the one you created before.
1{
2 "app": "python3 infra.py"
3}
The directory structure should look like this:
1project/
2├── app
3│ ├── main.py
4│ └── requirements.txt
5├── cdk.json
6├── infra.py
7├── requirements.txt
8└── stack.py
Deploy the stack
Before deploying, ensure that Docker is running.
1cdk deploy
After the stack is deployed, we’re given the API endpoint in a stack output:
1Outputs:
2ApiStack.APIEndpoint1793E782 = https://{api_id}.execute-api.us-east-1.amazonaws.com/prod/
Testing the proxy integration
We’ll use curl
to test our API:
1curl https://{api_id}.execute-api.us-east-1.amazonaws.com/prod/
Output:
1{"message": "Hello world!"}
The /tasks
endpoint:
1curl https://{api_id}.execute-api.us-east-1.amazonaws.com/prod/tasks
Output:
1[{"id":"ptvWZ3","text":"hello!"},{"id":"cqDUr3","text":"another!"}]
Testing locally
The top-level requirements.txt
enables us to test locally with uvicorn:
1uvicorn app.main:app --reload
Using curl
,
1curl http://127.0.0.1:8000/
Output:
1{"message":"Hello World!"}
Cleaning up
If you wish to destroy your CDK app, run the following command:
1cdk destroy
Conclusion
After following the prior steps, you will have an understanding of how to deploy a functioning API Gateway and Lambda function powered by FastAPI.
Enjoy building on this example!