Search code examples
aws-lambdaaws-api-gatewayfastapimangum

How do I correctly route FastAPI on AWS Lambda using Mangum to avoid a 'not found' error?


I have an AWS Lambda function which I can't seem to hook up to a custom domain using AWS API Gateway. I consistently get a {"detail":"Not Found"} error when I curl:

https://api.domain.com/api/v1/users
https://api.domain.com/api/v1/users/1

I added a catch all to return the method and the request with the code looking like this but I can't see how to match it to the returned url:

from fastapi import FastAPI, Request
from mangum import Mangum

app = FastAPI() # /api/v1/users/


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

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

@app.get("/users/{user_id}")
def user_item(user_id: int, q: str = None):
    return {"user_id": user_id, "q": q}

# My catch all code
#@app.api_route("/{path_name:path}", methods=["GET"])
#async def catch_all(request: Request, path_name: str):
#    return {"request_method": request.method, "path_name": path_name}

handler = Mangum(app, lifespan="off")

Which, when uncommented returns the path_name correctly:

{"request_method":"GET","path_name":"api/v1/users"}
{"request_method":"GET","path_name":"api/v1/users/1"}

I am totally stuck but I think it's something to do with either a prefix or a default root.


Solution

  • So 3 things were required to fix this:

    1. Firstly I needed to specify an api_gateway_base_path='/api/v1/users' so Mangum knew where the AWS root path of api.domain.com/api/v1/users was. This gave me no docs but did allow me to access /users/ and /users/whatever
    2. I then needed to specify the root_path="/api/v1/users" which then enabled me to access api.domain.com/api/v1/users/docs
    3. Unfortunately I still had one problem api.domain.com/api/v1/users was still "not found". In order to solve this I had to add an additional route @app.get('', include_in_schema=False) to capture routes which had no trailing forward slash. I added the False for include_in_schema so the automatic documentation system wasn't generating the same docs for the same route.

    Final code looked like this:

    from datetime import datetime
    
    from fastapi import FastAPI, Request
    from mangum import Mangum
    from pydantic import BaseModel
    
    app = FastAPI(
        root_path="/api/v1/users"
    )  
    
    @app.get('', include_in_schema=False)
    @app.get("/")
    def read_root(request: Request):
        return {"Hello": "Users"}
    
    
    
    # @app.api_route("/{path_name:path}", methods=["GET"])
    # async def catch_all(request: Request, path_name: str):
    #     print(request.scope)
    #     return {
    #         "request_method": request.method,
    #         "root_path": request.scope['root_path'],
    #         "raw_path": request.scope['raw_path'],
    #         "path": request.scope['path'],
    #         "path_name": path_name
            
    #     }
    
    handler = Mangum(
        app, 
        lifespan="off", 
        api_gateway_base_path='/api/v1/users'
    )