AWS Lambda Running FastAPI using Lambda Web Adapter

AWS Lambda Running FastAPI using Lambda Web Adapter

Takahiro Iwasa
Takahiro Iwasa
6 min read
Lambda Lambda Web Adapter

Lambda Web Adapter has drastically accelerated application development which uses AWS Lambda. It enables us to develop backend services on AWS Lambda using docker which most developers are accustomed to.

This post guides you through how to develop API backends with FastAPI using Lambda Web Adapter. You can clone the example from my GitHub repository.

Overview

Merits

I think that merits of Lambda Web Adapter are:

Comparison ItemsLambda Web AdapterTraditional Lambda
Testing locallyEasyHard
Migrating to other services like FargateEasyHard
Number of AWS resourcesLess 1More
CostLow (because of overhead)Very low
  1. There are only one Lambda function and one API Gateway route.

AWS Design

Traditional

The traditional AWS design using API Gateway and Lambda functions is like the following.

It might become chaos if you add many APIs.

Using Lambda Web Adapter

Lambda Web Adapter simplifies the AWS design like the following.

You do not need to take care of many API Gateway routes and Lambda functions.

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 of all, bootstrap your AWS CDK environment.

In this post, we install AWS CDK locally to avoid polluting the global npm environment.

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

Initializing CDK Project

To initialize your CDK project, run the following command.

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

Setting up FastAPI

To install FastAPI, run the following command.

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

Backend API using FastAPI

Writing API

In this post, we use the FastAPI example as it is. Write the following code and save it 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 API

You can start the server with the following command.

fastapi dev ./src/main.py

You would see the server running.

...

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [73992] using WatchFiles
INFO:     Started server process [73997]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

Then, hit the API using tools which you like. You should see the response returned.

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.

The key points here are:

By default, Lambda Web Adapter assumes the web app is listening on port 8080. If not, you can specify the port via configuration.

FROM public.ecr.aws/docker/library/python:3.12-alpine AS base
ENV APP_ROOT=/code
COPY ./src/requirements.txt $APP_ROOT/
RUN pip install --no-cache-dir --upgrade -r $APP_ROOT/requirements.txt

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

FROM base
ENV ENV=prod
EXPOSE 8080
COPY ./src $APP_ROOT
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter
CMD ["sh", "-c", "fastapi run $APP_ROOT/main.py --port 8080"]

Docker Compose for Local Development

Although the databases are not used in this post, they are almost always used, for example, DynamoDB, MySQL, etc. Thus, it would be useful for local development to prepare ./docker/compose.yaml.

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

You can run the backend service with the following command.

cd docker
docker compose up

...

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)

Deploying to AWS

Defining AWS Resources using AWS CDK

What you need to do is only to define AWS resources using AWS CDK.

Paste the following into cdk/bin/cdk.ts.

#!/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');

Paste the following into cdk/lib/cdk-stack.ts. You may be surprised about the less code.

Note that the following:

  • memorySize (line 19) larger than 128 should be better to avoid timeout.
  • platform: Platform.LINUX_AMD64 (line 22) is needed if you are using Apple Silicon; otherwise you would see the error saying Error: fork/exec /opt/extensions/lambda-adapter: exec format error Extension.LaunchError.
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, // To avoid timeout
      code: DockerImageCode.fromImageAsset(path.join(__dirname, '..', '..'), {
        file: path.join('docker', 'Dockerfile-web'),
        platform: Platform.LINUX_AMD64, // If you are using Apple Silicon
        exclude: ['*', '!src', '!docker'],
      }),
    });

    // API Gateway REST API
    new LambdaRestApi(this, 'api', {
      handler: lambda,
      deploy: true,
    });
  }
}

Deploying App

Finally, hit the following command. It may take up to a few minutes.

cd cdk
npx cdk deploy

...

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

Testing App

You can test your APIs using tools which you like.

curl "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/"
{"Hello":"World"}

curl "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/items/1?q=keyword"
{"item_id":1,"q":"keyword"}

Conclusion

When developing APIs with AWS Lambda, Lambda Web Adapter may be the first choice.

Before its introduction, developers faced challenges in testing locally and managing numerous API Gateway routes and Lambda functions. However, you can now test backend APIs as usual, with only one API Gateway route and one Lambda function to manage.

I hope you will find this post useful.

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.