Skip to content

FastAPI

FastAPI is a framework for building APIs in Python.

The FastAPI generator creates a new FastAPI with AWS CDK or Terraform infrastructure setup. The generated backend uses AWS Lambda for serverless deployment, exposed via an AWS API Gateway API. It sets up AWS Lambda Powertools for observability, including logging, AWS X-Ray tracing and Cloudwatch Metrics.

You can generate a new FastAPI in two ways:

  1. Install the Nx Console VSCode Plugin if you haven't already
  2. Open the Nx Console in VSCode
  3. Click Generate (UI) in the "Common Nx Commands" section
  4. Search for @aws/nx-plugin - py#fast-api
  5. Fill in the required parameters
    • Click Generate
    Parameter Type Default Description
    name Required string - Name of the API project to generate
    computeType string ServerlessApiGatewayRestApi The type of compute to use to deploy this API. Choose between ServerlessApiGatewayRestApi (default) or ServerlessApiGatewayHttpApi.
    integrationPattern string isolated How API Gateway integrations are generated for the API. Choose between isolated (default) and shared.
    auth string IAM The method used to authenticate with your API. Choose between IAM (default), Cognito or None.
    directory string packages The directory to store the application in.
    iacProvider string Inherit The preferred IaC provider. By default this is inherited from your initial selection.
    moduleName string - Python module name

    The generator will create the following project structure in the <directory>/<api-name> directory:

    • project.json Project configuration and build targets
    • pyproject.toml Python project configuration and dependencies
    • run.sh Lambda Web Adapter bootstrap script to start the FastAPI app via uvicorn
    • Directory<module_name>
      • __init__.py Module initialisation
      • init.py Sets the up FastAPI app and configures powertools middleware
      • main.py API implementation
    • Directoryscripts
      • generate_open_api.py Script to generate an OpenAPI schema from the FastAPI app

    Since this generator vends infrastructure as code based on your chosen iacProvider, it will create a project in packages/common which includes the relevant CDK constructs or Terraform modules.

    The common infrastructure as code project is structured as follows:

    • Directorypackages/common/constructs
      • Directorysrc
        • Directoryapp/ Constructs for infrastructure specific to a project/generator
        • Directorycore/ Generic constructs which are reused by constructs in app
        • index.ts Entry point exporting constructs from app
      • project.json Project build targets and configuration

    For deploying your API, the following files are generated:

    • Directorypackages/common/constructs/src
      • Directoryapp
        • Directoryapis
          • <project-name>.ts CDK construct for deploying your API
      • Directorycore
        • Directoryapi
          • http-api.ts CDK construct for deploying an HTTP API (if you selected to deploy an HTTP API)
          • rest-api.ts CDK construct for deploying a REST API (if you selected to deploy a REST API)
          • utils.ts Utilities for the API constructs

    The main API implementation is in main.py. This is where you define your API routes and their implementations. Here’s an example:

    from pydantic import BaseModel
    from .init import app, tracer
    class Item(BaseModel):
    name: str
    @app.get("/items/{item_id}")
    @tracer.capture_method
    def get_item(item_id: int) -> Item:
    return Item(name=...)
    @app.post("/items")
    @tracer.capture_method
    def create_item(item: Item):
    return ...

    The generator sets up several features automatically:

    1. AWS Lambda Powertools integration for observability
    2. Error handling middleware
    3. Request/response correlation
    4. Metrics collection
    5. AWS Lambda deployment via Lambda Web Adapter with uvicorn
    6. Type-safe streaming (REST API only)

    The generator configures structured logging using AWS Lambda Powertools. You can access the logger in your route handlers:

    from .init import app, logger
    @app.get("/items/{item_id}")
    def read_item(item_id: int):
    logger.info("Fetching item", extra={"item_id": item_id})
    return {"item_id": item_id}

    The logger automatically includes:

    • Correlation IDs for request tracing
    • Request path and method
    • Lambda context information
    • Cold start indicators

    AWS X-Ray tracing is configured automatically. You can add custom subsegments to your traces:

    from .init import app, tracer
    @app.get("/items/{item_id}")
    @tracer.capture_method
    def read_item(item_id: int):
    # Creates a new subsegment
    with tracer.provider.in_subsegment("fetch-item-details"):
    # Your logic here
    return {"item_id": item_id}

    CloudWatch metrics are collected automatically for each request. You can add custom metrics:

    from .init import app, metrics
    from aws_lambda_powertools.metrics import MetricUnit
    @app.get("/items/{item_id}")
    def read_item(item_id: int):
    metrics.add_metric(name="ItemViewed", unit=MetricUnit.Count, value=1)
    return {"item_id": item_id}

    Default metrics include:

    • Request counts
    • Success/failure counts
    • Cold start metrics
    • Per-route metrics

    The generator includes comprehensive error handling:

    from fastapi import HTTPException
    @app.get("/items/{item_id}")
    def read_item(item_id: int):
    if item_id < 0:
    raise HTTPException(status_code=400, detail="Item ID must be positive")
    return {"item_id": item_id}

    Unhandled exceptions are caught by the middleware and:

    1. Log the full exception with stack trace
    2. Record a failure metric
    3. Return a safe 500 response to the client
    4. Preserve the correlation ID

    The generated FastAPI supports streaming responses out of the box when using a REST API. The infrastructure is configured to use the AWS Lambda Web Adapter to run your FastAPI via uvicorn inside Lambda, with ResponseTransferMode.STREAM in API Gateway for all REST API operations, which enables streaming to work alongside non-streaming operations.

    The generated init.py exports a JsonStreamingResponse class that provides type-safe streaming with proper OpenAPI schema generation. This ensures that the connection generator can produce correctly typed streaming client methods.

    from pydantic import BaseModel
    from .init import app, JsonStreamingResponse
    class Chunk(BaseModel):
    message: str
    async def generate_chunks():
    for i in range(100):
    yield Chunk(message=f"This is chunk {i}")
    @app.post(
    "/stream",
    response_class=JsonStreamingResponse,
    responses={200: JsonStreamingResponse.openapi_response(Chunk, "Stream of chunks")},
    )
    async def my_stream() -> JsonStreamingResponse:
    return JsonStreamingResponse(generate_chunks())

    The JsonStreamingResponse class:

    1. Serializes Pydantic models to JSON Lines format (application/jsonl)
    2. Provides an openapi_response helper that generates the correct OpenAPI schema with itemSchema, enabling the connection generator to produce type-safe streaming client methods

    To consume a stream of responses, you can make use of the connection generator which will provide a type-safe method for iterating over your streamed chunks.

    The FastAPI generator creates CDK or Terraform infrastructure as code based on your selected iacProvider. You can use this to deploy your FastAPI.

    The CDK construct for deploying your API in the common/constructs folder. You can use this in a CDK application:

    import { MyApi } from ':my-scope/common-constructs';
    export class ExampleStack extends Stack {
    constructor(scope: Construct, id: string) {
    // Add the api to your stack
    const api = new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this).build(),
    });
    }
    }

    This sets up:

    1. An AWS Lambda function for each operation in the FastAPI application
    2. API Gateway HTTP/REST API as the function trigger
    3. IAM roles and permissions
    4. CloudWatch log group
    5. X-Ray tracing configuration
    6. CloudWatch metrics namespace

    The REST/HTTP API CDK constructs are configured to provide a type-safe interface for defining integrations for each of your operations.

    The CDK constructs provide full type-safe integration support as described below.

    You can use the static defaultIntegrations to make use of the default pattern, which defines an individual AWS Lambda function for each operation:

    new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this).build(),
    });

    You can access the underlying AWS Lambda functions via the API construct’s integrations property, in a type-safe manner. For example, if your API defines an operation named sayHello and you need to add some permissions to this function, you can do so as follows:

    const api = new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this).build(),
    });
    // sayHello is typed to the operations defined in your API
    api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({
    effect: Effect.ALLOW,
    actions: [...],
    resources: [...],
    }));

    If your API uses the shared pattern, the shared router Lambda is exposed as api.integrations.$router:

    const api = new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this).build(),
    });
    api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');

    If you would like to customise the options used when creating the Lambda function for each default integration, you can use the withDefaultOptions method. For example, if you would like all of your Lambda functions to reside in a Vpc:

    const vpc = new Vpc(this, 'Vpc', ...);
    new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this)
    .withDefaultOptions({
    vpc,
    })
    .build(),
    });

    You can also override integrations for specific operations using the withOverrides method. Each override must specify an integration property which is typed to the appropriate CDK integration construct for the HTTP or REST API. The withOverrides method is also type-safe. For example, if you would like to override a getDocumentation API to point to documentation hosted by some external website you could achieve this as follows:

    new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this)
    .withOverrides({
    getDocumentation: {
    integration: new HttpIntegration('https://example.com/documentation'),
    },
    })
    .build(),
    });

    You will also notice that the overridden integration no longer has a handler property when accessing it via api.integrations.getDocumentation.

    You can add additional properties to an integration which will also be typed accordingly, allowing for other types of integration to be abstracted but remain type-safe, for example if you have created an S3 integration for a REST API and later wish to reference the bucket for a particular operation, you can do so as follows:

    const storageBucket = new Bucket(this, 'Bucket', { ... });
    const apiGatewayRole = new Role(this, 'ApiGatewayS3Role', {
    assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
    });
    storageBucket.grantRead(apiGatewayRole);
    const api = new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this)
    .withOverrides({
    getFile: {
    bucket: storageBucket,
    integration: new AwsIntegration({
    service: 's3',
    integrationHttpMethod: 'GET',
    path: `${storageBucket.bucketName}/{fileName}`,
    options: {
    credentialsRole: apiGatewayRole,
    requestParameters: {
    'integration.request.path.fileName': 'method.request.querystring.fileName',
    },
    integrationResponses: [{ statusCode: '200' }],
    },
    }),
    options: {
    requestParameters: {
    'method.request.querystring.fileName': true,
    },
    methodResponses: [{
    statusCode: '200',
    }],
    }
    },
    })
    .build(),
    });
    // Later, perhaps in another file, you can access the bucket property we defined
    // in a type-safe manner
    api.integrations.getFile.bucket.grantRead(...);

    You can also supply options in your integration to override particular method options such as authorizers, for example if you wished to use Cognito authentication for your getDocumentation operation:

    new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this)
    .withOverrides({
    getDocumentation: {
    integration: new HttpIntegration('https://example.com/documentation'),
    options: {
    authorizer: new CognitoUserPoolsAuthorizer(...) // for REST, or HttpUserPoolAuthorizer for an HTTP API
    }
    },
    })
    .build(),
    });

    If you prefer, you can choose not to use the default integrations and instead directly supply one for each operation. This is useful if, for example, each operation needs to use a different type of integration or you would like to receive a type error when adding new operations:

    new MyApi(this, 'MyApi', {
    integrations: {
    sayHello: {
    integration: new LambdaIntegration(...),
    },
    getDocumentation: {
    integration: new HttpIntegration(...),
    },
    },
    });

    Generated CDK API constructs support two integration patterns:

    • isolated creates one Lambda function per operation. This is the default for generated APIs.
    • shared creates a single default router Lambda and reuses it for every operation unless you override specific integrations.

    isolated gives you finer-grained permissions and configuration per operation. shared reduces Lambda and API Gateway integration sprawl while still allowing selective overrides.

    For example, setting pattern to 'shared' creates a single function instead of one per integration:

    packages/common/constructs/src/app/apis/my-api.ts
    export class MyApi<...> extends ... {
    public static defaultIntegrations = (scope: Construct) => {
    ...
    return IntegrationBuilder.rest({
    pattern: 'shared',
    ...
    });
    };
    }

    Since operations in FastAPI are defined in Python and CDK infrastructure in TypeScript, we instrument code-generation to supply metadata to the CDK construct to provide a type-safe interface for integrations.

    A generate:<ApiName>-metadata target is added to the common constructs project.json to facilitate this code generation, which emits a file such as packages/common/constructs/src/generated/my-api/metadata.gen.ts. Since this is generated at build time, it is ignored in version control.

    If you selected to use IAM authentication, you can use the grantInvokeAccess method to grant access to your API:

    api.grantInvokeAccess(myIdentityPool.authenticatedRole);

    The generator configures a local development server that you can run with:

    Terminal window
    pnpm nx run my-api:serve

    This starts a local FastAPI development server with:

    • Auto-reload on code changes
    • Interactive API documentation at /docs or /redoc
    • OpenAPI schema at /openapi.json

    To invoke your API from a React website, you can use the connection generator.