How to Build a Serverless API with FastAPI and AWS Lambda
In modern cloud architecture, the directive is clear: maximize scalability and developer velocity while minimizing operational overhead. Traditional monolithic, server-based APIs impose a tax in management, scaling, and cost. Serverless architectures, specifically AWS Lambda, offer a compelling alternative by abstracting server management and providing true pay-per-invocation scaling.
However, the raw function-as-a-service (FaaS) model can lack the developer experience, structure, and performance optimization of modern web frameworks. This is where FastAPI enters. FastAPI, built on Starlette and Pydantic, provides a high-performance (on par with NodeJS and Go) asynchronous framework with automatic data validation and API documentation.
Combining FastAPI's exceptional developer experience with AWS Lambda's operational efficiency creates a powerful stack for building robust, scalable, and cost-effective APIs. This article provides a complete, production-ready blueprint for deploying a FastAPI application as an AWS Lambda function, fronted by an API Gateway, using the AWS Serverless Application Model (SAM).
Core Components of the Architecture
- FastAPI: The Python web framework. We leverage it for routing, dependency injection, Pydantic-based data validation, and automatic OpenAPI (Swagger/ReDoc) generation.
- AWS Lambda: The compute service. Our entire FastAPI application will be packaged and deployed as a single Lambda function.
- Amazon API Gateway (HttpApi): The entry point. We will use the v2 HTTP API (which is faster and cheaper than the v1 REST API) to receive HTTP requests and proxy them to our Lambda function.
- Mangum: The critical adapter. AWS Lambda and API Gateway communicate via a specific JSON event structure. FastAPI, as an ASGI framework, expects an ASGI scope.
Mangum
is a lightweight adapter that translates the Lambda event into an ASGI-compliant scope that FastAPI understands. - AWS SAM (Serverless Application Model): The Infrastructure as Code (IaC) framework. We use SAM to define our Lambda function, API Gateway, and their permissions in a simple
template.yaml
file, enabling reproducible and automated deployments.
Section 1: Project Setup and the FastAPI Application
First, establish the project structure. We will isolate the application code in an app/
directory.
serverless-fastapi/
├── app/
│ ├── __init__.py
│ ├── main.py
│ └── requirements.txt
└── template.yaml
1.1. Define Dependencies (app/requirements.txt
)
Our application requires three core libraries:
fastapi
uvicorn # Required by FastAPI, though we won't run it as a server
mangum # The Lambda-to-ASGI adapter
pydantic # For data modeling (often a peer dependency of fastapi)
1.2. Create the FastAPI Application (app/main.py
)
We will create a simple API with three endpoints to demonstrate core functionality:
GET /
: A root endpoint.POST /users/
: An endpoint demonstrating Pydantic-based request body validation.GET /users/{user_id}
: An endpoint demonstrating path parameters and a Pydantic response model.
# app/main.py
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
from typing import Optional
# 1. Define Pydantic Models for data validation
class UserCreate(BaseModel):
email: EmailStr
username: str
is_active: bool = True
class UserResponse(BaseModel):
id: int
email: EmailStr
username: str
# In-memory "database" for demonstration
db_users = {
1: {"id": 1, "email": "cto@example.com", "username": "chief-tech"},
2: {"id": 2, "email": "dev@example.com", "username": "lead-dev"},
}
# 2. Create the FastAPI app instance
app = FastAPI(
title="Serverless FastAPI Demo",
description="A demonstration of FastAPI on AWS Lambda.",
version="1.0.0",
)
@app.get("/")
def read_root():
"""
A simple health-check or root endpoint.
"""
return {"message": "FastAPI is running on AWS Lambda!"}
@app.get("/users/{user_id}", response_model=UserResponse)
def get_user(user_id: int):
"""
Get a user by their ID.
"""
user = db_users.get(user_id)
if not user:
return {"error": "User not found"}, 404
return user
@app.post("/users/", response_model=UserResponse, status_code=201)
def create_user(user: UserCreate):
"""
Create a new user.
The `user: UserCreate` parameter ensures the request body
is validated against the UserCreate Pydantic model.
"""
new_user_id = max(db_users.keys()) + 1
new_user = user.model_dump()
new_user["id"] = new_user_id
db_users[new_user_id] = new_user
# Note: Pydantic v2 uses model_dump(), v1 used .dict()
return new_user
This is a standard, clean FastAPI application. Critically, it has no knowledge of its serverless environment.
Section 2: Integrating Mangum - The ASGI Adapter
To allow Lambda to invoke our FastAPI app, we introduce Mangum
. We modify app/main.py
to add a Lambda handler.
Add the following lines to the bottom of app/main.py
:
# ... (all the FastAPI code from above) ...
# 3. Import Mangum and create the handler
from mangum import Mangum
# This 'handler' is the entry point for AWS Lambda.
# Mangum(app) creates an adapter that AWS Lambda can invoke.
handler = Mangum(app, lifespan="off")
# Note: lifespan="off" is recommended for simple Lambda functions
# to avoid potential issues with startup/shutdown events.
# For more complex needs (e.g., database connection pools),
# you may need to manage the 'on' lifespan carefully.
The handler
object is now the callable that AWS Lambda will execute. Mangum inspects the incoming event
dictionary from API Gateway, translates it into an ASGI scope, passes it to app
, awaits the response, and then formats FastAPI's response back into the dictionary structure API Gateway expects.
Section 3: Defining the Infrastructure with AWS SAM
We will now define our serverless infrastructure using the AWS SAM template.yaml
. This file declares our Lambda function and the HTTP API that triggers it.
Place this in the root of your project (template.yaml
):
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
A serverless application deploying a FastAPI backend on AWS Lambda.
Globals:
Function:
Timeout: 30
MemorySize: 256
Runtime: python3.11 # Specify a modern Python runtime
Resources:
# 1. The Lambda Function resource
FastApiLambdaFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: app/ # Points to the directory containing our app
Handler: main.handler # The file is 'main.py', the callable is 'handler'
Events:
# 2. The API Gateway (HttpApi) event source
ApiEvent:
Type: HttpApi
Properties:
# 3. The "catch-all" proxy integration. This is critical.
# It forwards ALL paths and methods to our Lambda.
Path: /{proxy+}
Method: ANY
# This payload format is required by Mangum
PayloadFormatVersion: '2.0'
Outputs:
# 4. Output the URL of the deployed API
ApiEndpoint:
Description: "API Gateway endpoint URL for Prod stage"
Value: !Sub "https"://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/
Architectural Decisions in this Template:
Handler: main.handler
: This tells Lambda to look inmain.py
for thehandler
object (which is ourMangum(app)
instance).Type: HttpApi
: We explicitly choose the v2 HTTP API for its lower latency and cost.Path: /{proxy+}
andMethod: ANY
: This is the most crucial part. This single integration creates a "catch-all" route. Any request, regardless of path (e.g.,/
,/users/1
,/docs
) or method (GET, POST, PUT), is proxied directly to the FastAPI application. This allows FastAPI's internal router to handle all routing logic, just as it would on a traditional server.PayloadFormatVersion: '2.0'
: Mangum works best with the v2 payload format, which is cleaner and more structured.
Section 4: Build and Deploy Procedure
With the app/
code and template.yaml
in place, we use the AWS SAM CLI to build and deploy.
Step 1: Install Dependencies and Build
SAM needs to package your application and its dependencies.
# First, install dependencies locally for SAM to package
# (SAM will copy these from app/ into its build directory)
pip install -r app/requirements.txt -t app/
# Next, build the SAM application
# --use-container is highly recommended. It builds your dependencies
# inside a Docker container that mimics the Lambda environment,
# preventing "glibc" or "openssl" errors from compiled libraries.
sam build --use-container
This command creates a .aws-sam/build
directory containing the final deployment package.
Step 2: Deploy the Application
We now deploy the built artifact to AWS.
# The --guided flag will walk you through a one-time setup
# for your stack name, AWS region, and other parameters.
sam deploy --guided
Follow the prompts:
- Stack Name:
serverless-fastapi-api
(or your preferred name). - AWS Region:
us-east-1
(or your target region). - Confirm changes before deploy:
y
. This allows you to review the AWS CloudFormation changeset. - Allow SAM CLI IAM role creation:
y
. - Save arguments to samconfig.toml:
y
. This saves your choices, so future deployments only requiresam deploy
.
SAM will now provision the CloudFormation stack, which creates the API Gateway, the Lambda function, and the necessary IAM roles. Upon completion, it will display the ApiEndpoint
from your template.yaml
outputs.
---------------------------------------------------------------------------------
Outputs
---------------------------------------------------------------------------------
Key ApiEndpoint
Description API Gateway endpoint URL for Prod stage
Value https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/
---------------------------------------------------------------------------------
Section 5: Verification and Testing
You can now interact with your fully serverless API. Let $API_URL
be the ApiEndpoint
value from your output.
Test 1: Root Endpoint
curl $API_URL/
Expected Response:
{"message":"FastAPI is running on AWS Lambda!"}
Test 2: POST with Pydantic Validation
curl -X POST "$API_URL/users/" \
-H "Content-Type: application/json" \
-d '{"email": "engineer@example.com", "username": "senior-engineer"}'
Expected Response:
{"id":3,"email":"engineer@example.com","username":"senior-engineer"}
Test 3: GET with Path Parameter
curl $API_URL/users/1
Expected Response:
{"id":1,"email":"cto@example.com","username":"chief-tech"}
Test 4: Automatic API Documentation
The most powerful feature: navigate to $API_URL/docs
in your browser. You will see the complete, interactive Swagger UI, automatically generated by FastAPI, running entirely on serverless infrastructure.
Section 6: Advanced Considerations for Production
1. Cold Starts
A "cold start" is the latency incurred on the first request to an idle Lambda function (downloading code, initializing the runtime and your application).
- Impact: FastAPI and Mangum are lightweight, so cold starts are typically fast (200-800ms). However, this can be significant for latency-critical applications.
- Mitigation: For p99 latency guarantees, enable Provisioned Concurrency on your Lambda function. This keeps a specified number of instances "warm" and ready, eliminating cold starts entirely, for an additional cost.
2. Dependency Management and Deployment Size
- Lambda Layers: If your application grows and shares large dependencies (e.g.,
pandas
,numpy
,scipy
) with other functions, move them into a Lambda Layer. This reduces the size of your function's deployment package, speeding up deployments and cold starts. - Container Images: For applications exceeding the 250MB (unzipped) .zip package limit, deploy your function as a Container Image. SAM supports this (
PackageType: Image
intemplate.yaml
). This allows for packages up to 10GB.
3. Observability
- Structured Logging: Do not use
print()
. Use a structured logging library. The AWS Lambda Powertools for Python is the industry standard. It provides structured logging, tracing (X-Ray), and custom metrics with minimal overhead. - Tracing: Enable AWS X-Ray active tracing on both API Gateway and your Lambda function (a simple
Tracing: Active
flag in yourtemplate.yaml
). This provides a complete trace of requests, from the gateway through the Lambda invocation and any downstream AWS SDK calls.
Conclusion
This architecture provides a "best-of-both-worlds" solution. We retain the entire feature set and world-class developer experience of FastAPI—including dependency injection, Pydantic validation, and automatic documentation—while simultaneously gaining the immense scalability, resilience, and pay-per-use cost model of AWS Lambda.
By leveraging Mangum for adaptation and AWS SAM for reproducible infrastructure, engineering teams can deploy robust, high-performance Python APIs with near-zero operational overhead, allowing them to focus purely on delivering business value.