Accelerate FastAPI Development on AWS Lambda with Lambda Web Adapter

Accelerate FastAPI Development on AWS Lambda with Lambda Web Adapter

Takahiro Iwasa
Takahiro Iwasa
7 min read
Lambda Lambda Web Adapter

Introduction

Lambda Web Adapter has revolutionized application development on AWS Lambda. It allows developers to build backend services using Docker, a tool familiar to most developers.

This post guides you through the process of developing API backends with FastAPI using Lambda Web Adapter. You can clone the example from my GitHub repository.

Overview

Merits of Lambda Web Adapter

Lambda Web Adapter offers several advantages over traditional AWS Lambda:

Comparison ItemsLambda Web AdapterTraditional Lambda
Testing locallyEasyHard
Migrating to other services (e.g., Fargate)EasyHard
Number of AWS resourcesLess1More
CostLowVery low

1 With Lambda Web Adapter, you only need one Lambda function and one API Gateway route.

AWS Design

Traditional Design

The traditional AWS design using API Gateway and multiple Lambda functions can become unwieldy when managing many APIs:

Design Using Lambda Web Adapter

Lambda Web Adapter simplifies the architecture, requiring only a single API Gateway route and one Lambda function:

Project Structure

This post creates the following project structure:

/
|-- .venv/
|-- cdk/
|   |-- bin/
|   |   `-- cdk.ts
|   |-- lib/
|   |   `-- cdk-stack.ts
|   |-- node_modules/
|   |-- test/
|   |   `-- cdk.test.ts
|   |-- .gitignore
|   |-- .npmignore
|   |-- biome.json
|   |-- cdk.json
|   |-- jest.config.js
|   |-- package.json
|   |-- package-lock.json
|   |-- README.md
|   `-- tsconfig.json
|-- docker/
|   |-- Dockerfile-web
|   `-- compose.yaml
|-- node_modules/
|-- src/
|   |-- main.py
|   `-- requirements.txt
|-- .gitignore
|-- package.json
`-- package-lock.json

Getting Started

Bootstrapping AWS CDK Environment

First, bootstrap your AWS CDK environment.

Follow the AWS CDK guide to install AWS CDK locally:

npm i -D aws-cdk
npx cdk bootstrap aws://<AWS_ACCOUNT_ID>/<AWS_REGION>

Initializing a CDK Project

To initialize your CDK project, run the following command:

mkdir cdk && cd cdk
npx cdk init app --language typescript

Setting up FastAPI

Install FastAPI with the following commands:

python -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]"
mkdir src
pip freeze > ./src/requirements.txt

Backend API using FastAPI

Writing an API

Here’s an example FastAPI application saved to ./src/main.py:

from typing import Union

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}

Testing the API

Start the FastAPI server with:

fastapi dev ./src/main.py

Test the API with your preferred tool:

curl "http://127.0.0.1:8000/"
{"Hello":"World"}

curl "http://127.0.0.1:8000/items/1?q=keyword"
{"item_id":1,"q":"keyword"}

Containerizing using Docker

Dockerfile

To containerize the FastAPI backend, write the following code and save it to ./docker/Dockerfile-web. This Dockerfile is designed to handle both development and production environments effectively.

Key points:

  • Base Image: We use public.ecr.aws/docker/library/python:3.12-alpine as our lightweight and secure base image (line 2).
  • Lambda Web Adapter: The Lambda Web Adapter is added for seamless AWS Lambda integration (line 22).
  • Port Configuration: The backend listens on port 8080 by default for production to match the Lambda Web Adapter’s expected configuration (line 25).

For more details on Lambda Web Adapter usage, refer to its official documentation.

# Base image: Python 3.12 Alpine
FROM public.ecr.aws/docker/library/python:3.12-alpine AS base
ENV APP_ROOT=/code

# Copy requirements and install dependencies
COPY ./src/requirements.txt $APP_ROOT/
RUN pip install --no-cache-dir --upgrade -r $APP_ROOT/requirements.txt

# Development stage
FROM base AS dev
ENV ENV=dev
EXPOSE 8000
CMD ["sh", "-c", "fastapi run $APP_ROOT/main.py --port 8000"]

# Production stage
FROM base
ENV ENV=prod
EXPOSE 8080
COPY ./src $APP_ROOT

# Copy Lambda Web Adapter
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter

# Run FastAPI backend on port 8080 for Lambda Web Adapter
CMD ["sh", "-c", "fastapi run $APP_ROOT/main.py --port 8080"]

In the next step, we will set up Docker Compose to streamline local development and testing.

Docker Compose for Local Development

While this post does not include database usage, in real-world scenarios, you often work with databases such as DynamoDB, MySQL, or others. To facilitate local development and testing, we can set up Docker Compose. This configuration ensures that your API backend runs smoothly in an isolated environment.

Create a ./docker/compose.yaml file with the following content:

services:
  api:
    build:
      context: ../
      dockerfile: ./docker/Dockerfile-web
      target: dev
    ports:
      - "8000:8000"
    volumes:
      - ../src:/code

Key Configuration Details

  • Build Context: Points to the project root (../) and uses the development stage of the Dockerfile (target: dev).
  • Ports Mapping: Maps the container’s port 8000 to the host’s port 8000 for local access.
  • Volumes: Mounts the local src directory to the container’s /code directory to enable hot-reloading of code during development.

Running the Backend Service

To start the backend service locally using Docker Compose, run the following commands:

cd docker
docker compose up

When the service starts successfully, you should see logs similar to:

api-1  | INFO:     Started server process [1]
api-1  | INFO:     Waiting for application startup.
api-1  | INFO:     Application startup complete.
api-1  | INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

You can now access the API at http://127.0.0.1:8000 and test it as usual using tools like curl, Postman, or a browser.

Next, we will explore deploying the FastAPI application to AWS Lambda using AWS CDK.

Deploying to AWS

Defining AWS Resources using AWS CDK

To deploy the FastAPI backend to AWS, define the necessary resources using AWS CDK. The CDK simplifies resource creation by allowing you to define infrastructure in code.

Step 1: Configure the Entry Point

Paste the following code into cdk/bin/cdk.ts. This is the entry point for your AWS CDK application.

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CdkStack } from '../lib/cdk-stack';

const app = new cdk.App();
new CdkStack(app, 'App');

Step 2: Define the Infrastructure Stack

In cdk/lib/cdk-stack.ts, define the AWS Lambda function and API Gateway. The following code creates a Dockerized Lambda function and integrates it with an API Gateway REST API.

import * as cdk from 'aws-cdk-lib';
import type { Construct } from 'constructs';
import { LambdaRestApi } from 'aws-cdk-lib/aws-apigateway';
import {
  DockerImageCode,
  DockerImageFunction,
  LoggingFormat,
} from 'aws-cdk-lib/aws-lambda';
import * as path from 'node:path';
import { Platform } from 'aws-cdk-lib/aws-ecr-assets';

export class CdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Dockerized Lambda Function
    const lambda = new DockerImageFunction(this, 'function', {
      functionName: 'fast-api-app-function',
      loggingFormat: LoggingFormat.JSON,
      memorySize: 512, // Larger memory to avoid timeout
      code: DockerImageCode.fromImageAsset(path.join(__dirname, '..', '..'), {
        file: path.join('docker', 'Dockerfile-web'),
        platform: Platform.LINUX_AMD64, // Required for Apple Silicon users
        exclude: ['*', '!src', '!docker'],
      }),
    });

    // API Gateway REST API
    new LambdaRestApi(this, 'api', {
      handler: lambda,
      deploy: true,
    });
  }
}
Keynotes
  • Memory Size: Set memorySize to 512 MB or higher (line 20). This prevents potential timeouts caused by insufficient memory.
  • Platform Configuration: Use Platform.LINUX_AMD64 (line 23) if you are using Apple Silicon. Without this, you may encounter the error: Error: fork/exec /opt/extensions/lambda-adapter: exec format error Extension.LaunchError.

With these files in place, you’re ready to deploy your resources.

Step 3: Deploy the Resources

Navigate to your CDK project directory and run the following command to deploy:

cd cdk
npx cdk deploy

During deployment, you may be prompted to confirm resource creation. Respond with y to proceed:

Do you wish to deploy these changes (y/n)? y
App: deploying... [1/1]
App: creating CloudFormation changeset...

 ✅  App

✨  Deployment time: 52.67s

Outputs:
App.apiEndpoint9349E63C = https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/
Stack ARN:
arn:aws:cloudformation:<AWS_REGION>:<AWS_ACCOUNT_ID>:stack/App/<UUID>

✨  Total time: 55.42s

The deployment will output the API Gateway endpoint, which you can use to test your API.

Testing the App

Test the deployed APIs:

curl "https://<API_GATEWAY_ENDPOINT>/prod/"
{"Hello":"World"}

curl "https://<API_GATEWAY_ENDPOINT>/prod/items/1?q=keyword"
{"item_id":1,"q":"keyword"}

Conclusion

Lambda Web Adapter significantly simplifies API development on AWS Lambda by reducing complexity and facilitating local testing capabilities. With its ability to streamline AWS architectures, it is an invaluable tool for modern web development.

Happy Coding! 🚀

Takahiro Iwasa

Takahiro Iwasa

Software Developer at KAKEHASHI Inc.
Involved in the requirements definition, design, and development of cloud-native applications using AWS. Now, building a new prescription data collection platform at KAKEHASHI Inc. Japan AWS Top Engineers 2020-2023.