What is FastAPI ?
FastAPI is a modern, fast, Python web framework for building APIs with first-class support for Pydantic data models and validation. It has quickly become one of the most popular frameworks in the web ecosystem and is in production use by many large organisations such as Netflix and Uber.
Additionally, FastAPI supports asynchronous programming with async/await syntax, making it easy to write efficient and scalable code. Other key features include built-in support for OpenAPI (formerly Swagger) documentation, WebSocket support, and integration with popular libraries like SQLAlchemy and Django ORM. Overall, FastAPI's unique combination of performance, ease of use, and strong typing makes it an attractive choice for building modern APIs in Python.
Building the application
The repository containing the code for this demo application can be found on Github, and is ready to clone and deploy with a simple cdk deploy
command. Just remember that you will incur some (tiny) AWS charges by using the application!
Architecture
Being a small demo application, the system architecture is extremely simple and includes:
- DynamoDB - the persistence layer of the application
- Lambda - where the FastAPI application will actually run and handle incoming requests
- API Gateway - provides the HTTP endpoint that can be reached via the internet
Infrastructure code
As the infrastructure required for the application is minimal, we define it in one CDK stack within api/infrastructure.py
.
DynamoDB table
First, we define the DynamoDB table including the name and data type of the attributes used as partition and sort keys. We leave all other arguments such as billing_mode
and encryption
as default.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
Lambda function
To package and deploy the FastAPI application, we use the Function
and Code
constructs.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
The Code.from_asset()
method uses an AWS provided Docker image and a magical cmd
incantation to bundle the application code in the correct way to be deployed on Lambda; this tweet from Eric Johnson provides a more detailed explanation of what happens under the hood during the bundling process.
Did you know, when using the CDK to create a Lambda function, you can use the same docker images to compile the artifacts that AWS SAM uses? pic.twitter.com/wkoHu5SnT5
— Eric Johnson (@edjgeek) July 11, 2024
For simplicity, we pass in the DynamoDB table name to the Lambda function as an environment variable, however, in production usage and/or for more sensitive credentials, it would be more secure to use AWS Secrets Manager or Parameter Store.
FastAPI application
The core api/v1
directory contains the actual FastAPI application code with the following directory structure:
βββ πv1
βββ __init__.py
βββ main.py
βββ models.py
βββ requirements.txt
βββ πrouters
βββ __init__.py
βββ songs.py
There is nothing special or complex about the API itself, users can interact with a Song
model via three endpoints that are exposed:
- Create
- List
- Delete (all)
main.py
This is where the FastAPI app
is defined and the top-level routers and routes are configured.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
We route the application under a top-level /v1
route which is a good practice to allow for API versioning, preventing the need to introduce breaking changes in the future. The songs
router gets included under that, exposing a CRUD-like interface to interact with the data.
Importantly, we need to account for the fact that Lambda is invoked by event triggers but FastAPI expects standard HTTP requests; we use mangum to transform the Lambda event payloads into HTTP requests that can be routed and handled by FastAPI.
models.py
This is where we define our Song
data model.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
Famously, the DynamoDB boto3
client is extremely verbose, so we use dyntastic, an ORM-like wrapper around the boto3
client with builtin Pydantic validation and type hinting.
We pull the table name and host from environment variables to make it easier to use in production as well as local development. Of note is the use of older DynamoDB terminology for the components of a compound primary key: partition/hash key and sort/range key. All the attributes are defined as class variables, with data types and default values included.
routers/songs.py
Finally, we define our endpoints to interact with the song data - for this demo we simply have a create
, list
and delete
endpoint.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
Conclusion
In this post, we've demonstrated how to take a simple FastAPI application, configure it to use DynamoDB for data persistence, and deploy it serverlessly to AWS Lambda using CDK.
In an upcoming post we'll be tackling the more complicated case of using a Postgres RDS instance as the database and handling migrations in a sensible, automated fashion - stay tuned!