Saltearse al contenido

FastAPI

FastAPI es un framework para construir APIs en Python.

El generador de FastAPI crea una nueva aplicación FastAPI con configuración de infraestructura en AWS CDK o Terraform. El backend generado utiliza AWS Lambda para implementación sin servidor, expuesto a través de una API de AWS API Gateway. Configura AWS Lambda Powertools para observabilidad, incluyendo logging, trazado con AWS X-Ray y métricas de CloudWatch.

Puedes generar una nueva API FastAPI de dos formas:

  1. Instale el Nx Console VSCode Plugin si aún no lo ha hecho
  2. Abra la consola Nx en VSCode
  3. Haga clic en Generate (UI) en la sección "Common Nx Commands"
  4. Busque @aws/nx-plugin - py#fast-api
  5. Complete los parámetros requeridos
    • Haga clic en Generate
    Parámetro Tipo Predeterminado Descripción
    name Requerido 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.
    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 CDK The preferred IaC provider
    moduleName string - Python module name

    El generador creará la siguiente estructura de proyecto en el directorio <directory>/<api-name>:

    • project.json Configuración del proyecto y objetivos de build
    • pyproject.toml Configuración del proyecto Python y dependencias
    • Directory<module_name>
      • __init__.py Inicialización del módulo
      • init.py Configura la aplicación FastAPI y el middleware de powertools
      • main.py Implementación de la API
    • Directoryscripts
      • generate_open_api.py Script para generar esquema OpenAPI desde la app FastAPI

    Dado que este generador proporciona infraestructura como código basada en tu proveedor de iacProvider seleccionado, creará un proyecto en packages/common que incluye los constructos CDK o módulos de Terraform correspondientes.

    El proyecto común de infraestructura como código tiene la siguiente estructura:

    • Directorypackages/common/constructs
      • Directorysrc
        • Directoryapp/ Constructos para infraestructura específica de un proyecto/generador
        • Directorycore/ Constructos genéricos reutilizados por los constructos en app
        • index.ts Punto de entrada que exporta los constructos de app
      • project.json Objetivos de compilación y configuración del proyecto

    Para implementar tu API, se generan los siguientes archivos:

    • Directorypackages/common/constructs/src
      • Directoryapp
        • Directoryapis
          • <project-name>.ts Constructo de CDK para implementar tu API
      • Directorycore
        • Directoryapi
          • http-api.ts Constructo de CDK para implementar una API HTTP (si seleccionaste implementar una API HTTP)
          • rest-api.ts Constructo de CDK para implementar una API REST (si seleccionaste implementar una API REST)
          • utils.ts Utilidades para los constructos de API

    La implementación principal de la API está en main.py. Aquí es donde defines las rutas de tu API y sus implementaciones. Ejemplo:

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

    El generador configura automáticamente varias características:

    1. Integración de AWS Lambda Powertools para observabilidad
    2. Middleware de manejo de errores
    3. Correlación de solicitudes/respuestas
    4. Recolección de métricas
    5. Manejador de AWS Lambda usando Mangum

    El generador configura logging estructurado usando AWS Lambda Powertools. Puedes acceder al logger en tus manejadores de rutas:

    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}

    El logger incluye automáticamente:

    • IDs de correlación para trazado de solicitudes
    • Ruta y método de la solicitud
    • Información del contexto de Lambda
    • Indicadores de cold start

    El trazado con AWS X-Ray se configura automáticamente. Puedes añadir subsegmentos personalizados:

    from .init import app, tracer
    @app.get("/items/{item_id}")
    @tracer.capture_method
    def read_item(item_id: int):
    # Crea un nuevo subsegmento
    with tracer.provider.in_subsegment("fetch-item-details"):
    # Tu lógica aquí
    return {"item_id": item_id}

    Las métricas de CloudWatch se recogen automáticamente para cada solicitud. Puedes añadir métricas personalizadas:

    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}

    Las métricas por defecto incluyen:

    • Conteo de solicitudes
    • Conteos de éxito/fallo
    • Métricas de cold start
    • Métricas por ruta

    El generador incluye manejo de errores completo:

    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}

    Las excepciones no capturadas son manejadas por el middleware y:

    1. Registran la excepción completa con stack trace
    2. Registran una métrica de fallo
    3. Devuelven una respuesta segura 500 al cliente
    4. Preservan el ID de correlación

    Con FastAPI, puedes transmitir una respuesta al cliente usando el tipo de respuesta StreamingResponse.

    Dado que AWS API Gateway no soporta respuestas en streaming, necesitarás desplegar tu FastAPI en una plataforma que lo soporte. La opción más simple es usar una URL de función Lambda de AWS.

    Para esto, puedes reemplazar el constructo generado common/constructs/src/app/apis/<name>-api.ts por uno que despliegue una Function URL.

    Ejemplo de Constructo FunctionURL para Streaming
    import { Duration, Stack, CfnOutput } from 'aws-cdk-lib';
    import { IGrantable, Grant } from 'aws-cdk-lib/aws-iam';
    import {
    Runtime,
    Code,
    Tracing,
    LayerVersion,
    FunctionUrlAuthType,
    InvokeMode,
    Function,
    } from 'aws-cdk-lib/aws-lambda';
    import { Construct } from 'constructs';
    import url from 'url';
    import { RuntimeConfig } from '../../core/runtime-config.js';
    export class MyApi extends Construct {
    public readonly handler: Function;
    constructor(scope: Construct, id: string) {
    super(scope, id);
    this.handler = new Function(this, 'Handler', {
    runtime: Runtime.PYTHON_3_12,
    handler: 'run.sh',
    code: Code.fromAsset(
    url.fileURLToPath(
    new URL(
    '../../../../../../dist/packages/my_api/bundle',
    import.meta.url,
    ),
    ),
    ),
    timeout: Duration.seconds(30),
    tracing: Tracing.ACTIVE,
    environment: {
    AWS_CONNECTION_REUSE_ENABLED: '1',
    },
    });
    const stack = Stack.of(this);
    this.handler.addLayers(
    LayerVersion.fromLayerVersionArn(
    this,
    'LWALayer',
    `arn:aws:lambda:${stack.region}:753240598075:layer:LambdaAdapterLayerX86:24`,
    ),
    );
    this.handler.addEnvironment('PORT', '8000');
    this.handler.addEnvironment('AWS_LWA_INVOKE_MODE', 'response_stream');
    this.handler.addEnvironment('AWS_LAMBDA_EXEC_WRAPPER', '/opt/bootstrap');
    const functionUrl = this.handler.addFunctionUrl({
    authType: FunctionUrlAuthType.AWS_IAM,
    invokeMode: InvokeMode.RESPONSE_STREAM,
    cors: {
    allowedOrigins: ['*'],
    allowedHeaders: [
    'authorization',
    'content-type',
    'x-amz-content-sha256',
    'x-amz-date',
    'x-amz-security-token',
    ],
    },
    });
    new CfnOutput(this, 'MyApiUrl', { value: functionUrl.url });
    // Registrar la URL en configuración para descubrimiento de clientes
    RuntimeConfig.ensure(this).config.apis = {
    ...RuntimeConfig.ensure(this).config.apis!,
    MyApi: functionUrl.url,
    };
    }
    public grantInvokeAccess(grantee: IGrantable) {
    Grant.addToPrincipal({
    grantee,
    actions: ['lambda:InvokeFunctionUrl'],
    resourceArns: [this.handler.functionArn],
    conditions: {
    StringEquals: {
    'lambda:FunctionUrlAuthType': 'AWS_IAM',
    },
    },
    });
    }
    }

    Una vez actualizada la infraestructura para soportar streaming, puedes implementar una API de streaming en FastAPI. La API debe:

    • Devolver un StreamingResponse
    • Declarar el tipo de cada chunk de respuesta
    • Añadir la extensión OpenAPI x-streaming: true si planeas usar API Connection.

    Ejemplo para transmitir objetos JSON:

    from pydantic import BaseModel
    from fastapi.responses import StreamingResponse
    class Chunk(BaseModel):
    message: str
    timestamp: datetime
    async def stream_chunks():
    for i in range(0, 100):
    yield Chunk(message=f"This is chunk {i}", timestamp=datetime.now())
    @app.get("/stream", openapi_extra={'x-streaming': True})
    def my_stream() -> Chunk:
    return StreamingResponse(stream_chunks(), media_type="application/json")

    Para consumir streams, puedes usar el Generador de Conexión API que provee métodos tipados para iterar chunks.

    El generador crea infraestructura como código CDK o Terraform según tu iacProvider. Puedes usarlo para desplegar tu API.

    El constructo CDK para desplegar tu API está en common/constructs. Puedes usarlo en una aplicación CDK:

    import { MyApi } from ':my-scope/common-constructs';
    export class ExampleStack extends Stack {
    constructor(scope: Construct, id: string) {
    // Añade la API al stack
    const api = new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this).build(),
    });
    }
    }

    Esto configura:

    1. Función AWS Lambda para cada operación
    2. API Gateway HTTP/REST API como trigger
    3. Roles y permisos IAM
    4. Grupo de logs CloudWatch
    5. Configuración de trazado X-Ray
    6. Namespace de métricas CloudWatch

    Los constructos CDK de la API REST/HTTP están configurados para proporcionar una interfaz type-safe que define integraciones para cada una de tus operaciones.

    Los constructos CDK proporcionan soporte completo de integración con seguridad de tipos como se describe a continuación.

    Puedes usar el método estático defaultIntegrations para utilizar el patrón por defecto, que define una función AWS Lambda individual para cada operación:

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

    Puedes acceder a las funciones AWS Lambda subyacentes a través de la propiedad integrations del constructo de la API, de manera type-safe. Por ejemplo, si tu API define una operación llamada sayHello y necesitas agregar permisos a esta función, puedes hacerlo así:

    const api = new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this).build(),
    });
    // sayHello está tipado según las operaciones definidas en tu API
    api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({
    effect: Effect.ALLOW,
    actions: [...],
    resources: [...],
    }));

    Si deseas personalizar las opciones usadas al crear la función Lambda para cada integración por defecto, puedes usar el método withDefaultOptions. Por ejemplo, si quieres que todas tus funciones Lambda residan en una VPC:

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

    También puedes sobrescribir integraciones para operaciones específicas usando el método withOverrides. Cada override debe especificar una propiedad integration que está tipada al constructo de integración CDK apropiado para la API HTTP o REST. El método withOverrides también es type-safe. Por ejemplo, si quieres sobrescribir una API getDocumentation para apuntar a documentación alojada en un sitio externo:

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

    Notarás que la integración sobrescrita ya no tiene una propiedad handler cuando se accede a través de api.integrations.getDocumentation.

    Puedes agregar propiedades adicionales a una integración que también estarán tipadas, permitiendo abstraer otros tipos de integración manteniendo el type-safe. Por ejemplo, si creaste una integración S3 para una API REST y luego quieres referenciar el bucket para una operación particular:

    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(),
    });
    // Más tarde, quizás en otro archivo, puedes acceder a la propiedad bucket que definimos
    // de manera type-safe
    api.integrations.getFile.bucket.grantRead(...);

    También puedes proveer options en tu integración para sobrescribir opciones de método específicas como autorizadores. Por ejemplo, si deseas usar autenticación con Cognito para tu operación getDocumentation:

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

    Si prefieres, puedes no usar las integraciones por defecto y proveer directamente una para cada operación. Esto es útil si, por ejemplo, cada operación necesita usar un tipo diferente de integración o quieres recibir un error de tipo al agregar nuevas operaciones:

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

    Si prefieres desplegar una única función Lambda para manejar todas las solicitudes de la API, puedes modificar libremente el método defaultIntegrations de tu API para crear una sola función en lugar de una por integración:

    packages/common/constructs/src/app/apis/my-api.ts
    export class MyApi<...> extends ... {
    public static defaultIntegrations = (scope: Construct) => {
    const router = new Function(scope, 'RouterHandler', { ... });
    return IntegrationBuilder.rest({
    ...
    defaultIntegrationOptions: {},
    buildDefaultIntegration: (op) => {
    return {
    // Referenciar el mismo router lambda handler en cada integración
    integration: new LambdaIntegration(router),
    };
    },
    });
    };
    }

    Puedes modificar el código de otras formas si prefieres, por ejemplo definiendo la función router como parámetro de defaultIntegrations en lugar de construirla dentro del método.

    Como las operaciones en FastAPI están en Python y CDK en TypeScript, usamos generación de código para proveer metadatos al constructo CDK.

    Se añade un target generate:<ApiName>-metadata al project.json de los constructos, que genera archivos como packages/common/constructs/src/generated/my-api/metadata.gen.ts. Este archivo se ignora en control de versiones.

    Si usas autenticación IAM, puedes usar grantInvokeAccess para conceder acceso:

    api.grantInvokeAccess(myIdentityPool.authenticatedRole);

    El generador configura un servidor de desarrollo local que puedes ejecutar con:

    Terminal window
    pnpm nx run my-api:serve

    Esto inicia un servidor FastAPI local con:

    • Recarga automática
    • Documentación interactiva en /docs o /redoc
    • Esquema OpenAPI en /openapi.json

    Para invocar tu API desde un sitio React, usa el generador api-connection.