FastAPI
FastAPI es un framework para construir APIs en Python.
El generador de FastAPI crea una nueva FastAPI con configuración de infraestructura AWS CDK o Terraform. El backend generado utiliza AWS Lambda para despliegue serverless, expuesto a través de una API de AWS API Gateway. Configura AWS Lambda Powertools para observabilidad, incluyendo logging, trazado de AWS X-Ray y métricas de Cloudwatch.
Generar una FastAPI
Sección titulada «Generar una FastAPI»Puedes generar una nueva FastAPI de dos maneras:
pnpm nx g @aws/nx-plugin:py#api --framework=fastapiyarn nx g @aws/nx-plugin:py#api --framework=fastapinpx nx g @aws/nx-plugin:py#api --framework=fastapibunx nx g @aws/nx-plugin:py#api --framework=fastapiTambién puede realizar una ejecución en seco para ver qué archivos se cambiarían
pnpm nx g @aws/nx-plugin:py#api --framework=fastapi --dry-runyarn nx g @aws/nx-plugin:py#api --framework=fastapi --dry-runnpx nx g @aws/nx-plugin:py#api --framework=fastapi --dry-runbunx nx g @aws/nx-plugin:py#api --framework=fastapi --dry-run- Instale el Nx Console VSCode Plugin si aún no lo ha hecho
- Abra la consola Nx en VSCode
- Haga clic en
Generate (UI)en la sección "Common Nx Commands" - Busque
@aws/nx-plugin - py#api - Complete los parámetros requeridos
- framework: fastapi
- Haga clic en
Generate
Opciones
Sección titulada «Opciones»| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
| name Requerido | string | - | Nombre del proyecto de API a generar |
| framework | fastapi | fastapi | El framework de API a utilizar. |
| integrationPattern | isolated | shared | isolated | Cómo se generan las integraciones de API Gateway para la API. Elija entre isolated (predeterminado) y shared. |
| auth | iam | cognito | custom | iam | El método utilizado para autenticar con tu API. Elige entre iam (predeterminado), cognito o custom. |
| directory | string | packages | El directorio donde almacenar la aplicación. |
| subDirectory | string | - | El subdirectorio en el que se coloca el proyecto. Por defecto, este es el nombre del proyecto. |
| iac | inherit | cdk | terraform | inherit | El proveedor de IaC preferido. Por defecto, se hereda de tu selección inicial. |
| moduleName | string | - | Nombre del módulo Python |
| infra | rest-lambda | http-lambda | none | rest-lambda | El tipo de infraestructura a utilizar para desplegar esta API. |
| preferInstallDependencies | boolean | true | Si se prefiere instalar las dependencias después de que se ejecute el generador. Establecer en false para diferir la instalación al procesar múltiples generadores en lote (la instalación aún se ejecuta si es necesario para que los generadores subsecuentes puedan calcular el grafo de proyectos de Nx); instalar una vez al final. |
Salida del Generador
Sección titulada «Salida del Generador»El generador creará la siguiente estructura de proyecto en el directorio <directory>/<api-name>:
- project.json Configuración del proyecto y objetivos de construcción
- pyproject.toml Configuración del proyecto Python y dependencias
- run.sh Script de arranque de Lambda Web Adapter para iniciar la aplicación FastAPI a través de uvicorn
Directorio<module_name>
- __init__.py Inicialización del módulo
- init.py Configura la aplicación FastAPI y configura el middleware de powertools
- main.py Implementación de la API
Directorioscripts
- generate_open_api.py Script para generar un esquema OpenAPI desde la aplicación FastAPI
Infraestructura
Sección titulada «Infraestructura»Dado que este generador proporciona infraestructura como código basada en tu iac elegido, creará un proyecto en packages/common que incluye las construcciones CDK o módulos Terraform relevantes.
El proyecto común de infraestructura como código está estructurado de la siguiente manera:
Directoriopackages/common/constructs
Directoriosrc
Directorioapp/ Constructs for infrastructure specific to a project/generator
- …
Directoriocore/ Generic constructs which are reused by constructs in
app- …
- index.ts Entry point exporting constructs from
app
- project.json Project build targets and configuration
Directoriopackages/common/terraform
Directoriosrc
Directorioapp/ Terraform modules for infrastructure specific to a project/generator
- …
Directoriocore/ Generic modules which are reused by modules in
app- …
- project.json Project build targets and configuration
Para desplegar tu API, se generan los siguientes archivos:
Directoriopackages/common/constructs/src
Directorioapp
Directorioapis
- <project-name>.ts CDK construct for deploying your API
Directoriocore
Directorioapi
- 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
Directoriopackages/common/terraform/src
Directorioapp
Directorioapis
Directorio<project-name>
- <project-name>.tf Module for deploying your API
Directoriocore
Directorioapi
Directoriohttp-api
- http-api.tf Module for deploying an HTTP API (if you selected to deploy an HTTP API)
Directoriorest-api
- rest-api.tf Module for deploying a REST API (if you selected to deploy a REST API)
Arquitectura
Sección titulada «Arquitectura»La aplicación desplegada tiene la siguiente arquitectura:
Las REST APIs incluyen una Web ACL de AWS WAFv2 frente a la etapa de API Gateway con el conjunto de reglas predeterminado administrado por AWS habilitado.
Las HTTP APIs no admiten WAF directamente — si necesitas protección WAF, elige REST API en su lugar o coloca la HTTP API detrás de una distribución de CloudFront.
Implementar tu FastAPI
Sección titulada «Implementar tu FastAPI»La implementación principal de la API está en main.py. Aquí es donde defines tus rutas de API y sus implementaciones. Aquí hay un ejemplo:
from pydantic import BaseModelfrom .init import app, tracer
class Item(BaseModel): name: str
@app.get("/items/{item_id}")@tracer.capture_methoddef get_item(item_id: int) -> Item: return Item(name=...)
@app.post("/items")@tracer.capture_methoddef create_item(item: Item): return ...El generador configura varias características automáticamente:
- Integración de AWS Lambda Powertools para observabilidad
- Middleware de manejo de errores
- Correlación de solicitud/respuesta
- Recopilación de métricas
- Despliegue de AWS Lambda a través de Lambda Web Adapter con uvicorn
- Streaming con seguridad de tipos (solo API REST)
Observabilidad con AWS Lambda Powertools
Sección titulada «Observabilidad con AWS Lambda Powertools»Logging
Sección titulada «Logging»El generador configura logging estructurado usando AWS Lambda Powertools. Puedes acceder al logger en tus manejadores de ruta:
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 el rastreo de solicitudes
- Ruta de solicitud, ruta coincidente y método
Trazado
Sección titulada «Trazado»El trazado de AWS X-Ray se configura automáticamente. Puedes agregar subsegmentos personalizados a tus trazas:
from .init import app, tracer
@app.get("/items/{item_id}")@tracer.capture_methoddef 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}Métricas
Sección titulada «Métricas»Las métricas de CloudWatch se recopilan automáticamente para cada solicitud. Puedes agregar métricas personalizadas:
from .init import app, metricsfrom 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 predeterminadas incluyen:
- Conteos de solicitudes
- Conteos de éxito/fallo
- Métricas por ruta (a través de una dimensión
routede<method> <path>)
Manejo de Errores
Sección titulada «Manejo de Errores»El generador incluye manejo de errores integral:
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 manejadas son capturadas por el middleware y:
- Registran la excepción completa con el stack trace
- Registran una métrica de fallo
- Devuelven una respuesta 500 segura al cliente
- Preservan el ID de correlación
Acceder al Usuario que Llama
Sección titulada «Acceder al Usuario que Llama»Cuando tu API está protegida por autenticación, tus manejadores de ruta a menudo necesitan saber quién está llamando. La FastAPI generada se ejecuta dentro de AWS Lambda a través del Lambda Web Adapter, que reenvía el contexto de solicitud de API Gateway como JSON en el encabezado x-amzn-request-context. Puedes leerlo desde el Request de FastAPI para extraer la identidad del llamador.
Como ejemplo, agreguemos un endpoint /me que devuelva detalles sobre el usuario que llama. Implementaremos la extracción como una dependencia de FastAPI para que pueda reutilizarse en todas las rutas. La forma del contexto de solicitud — y por lo tanto cómo extraes la identidad — depende tanto de tu método auth seleccionado como de si desplegaste una API REST o HTTP.
Para autenticación IAM, buscamos al llamador en Cognito usando el sub extraído del contexto de solicitud de API Gateway. Crea identity.py junto a main.py:
import jsonimport osfrom typing import Annotated
from boto3 import clientfrom fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
cognito = client("cognito-idp")
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # The Lambda Web Adapter forwards the API Gateway request context as JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) provider = request_context.get("identity", {}).get("cognitoAuthenticationProvider")
sub = provider.split(":")[-1] if provider else None if not sub: raise HTTPException(status_code=403, detail="Unable to determine calling user")
users = cognito.list_users( # Assumes user pool id is configured in lambda environment UserPoolId=os.environ["USER_POOL_ID"], Limit=1, Filter=f'sub="{sub}"', ).get("Users", [])
if len(users) != 1: raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
return Identity(sub=sub, username=users[0]["Username"])
CurrentUser = Annotated[Identity, Depends(get_identity)]import jsonimport osfrom typing import Annotated
from boto3 import clientfrom fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
cognito = client("cognito-idp")
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # The Lambda Web Adapter forwards the API Gateway request context as JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) amr = ( request_context.get("authorizer", {}) .get("iam", {}) .get("cognitoIdentity", {}) .get("amr", []) ) sign_in = next((s for s in amr if ":CognitoSignIn:" in s), None) sub = sign_in.split(":")[-1] if sign_in else None
if not sub: raise HTTPException(status_code=403, detail="Unable to determine calling user")
users = cognito.list_users( # Assumes user pool id is configured in lambda environment UserPoolId=os.environ["USER_POOL_ID"], Limit=1, Filter=f'sub="{sub}"', ).get("Users", [])
if len(users) != 1: raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
return Identity(sub=sub, username=users[0]["Username"])
CurrentUser = Annotated[Identity, Depends(get_identity)]Con auth: 'cognito', el autorizador de Cognito User Pools de API Gateway verifica el JWT que el llamador proporciona en el encabezado Authorization y coloca las reclamaciones verificadas en el contexto de solicitud.
Crea identity.py junto a main.py:
import jsonfrom typing import Annotated
from fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # The Lambda Web Adapter forwards the API Gateway request context as JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) claims = request_context.get("authorizer", {}).get("claims", {})
sub = claims.get("sub") username = claims.get("username")
if not sub or not username: raise HTTPException(status_code=403, detail="Unable to determine calling user")
return Identity(sub=sub, username=username)
CurrentUser = Annotated[Identity, Depends(get_identity)]Las APIs HTTP usan un autorizador JWT que coloca las reclamaciones verificadas bajo authorizer.jwt.claims:
import jsonfrom typing import Annotated
from fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # The Lambda Web Adapter forwards the API Gateway request context as JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) claims = request_context.get("authorizer", {}).get("jwt", {}).get("claims", {})
sub = claims.get("sub") username = claims.get("username")
if not sub or not username: raise HTTPException(status_code=403, detail="Unable to determine calling user")
return Identity(sub=sub, username=username)
CurrentUser = Annotated[Identity, Depends(get_identity)]Luego puedes inyectar la dependencia CurrentUser en cualquier ruta que necesite la identidad del llamador:
from .identity import CurrentUser, Identityfrom .init import app, tracer
@app.get("/me")@tracer.capture_methoddef me(identity: CurrentUser) -> Identity: return identityStreaming
Sección titulada «Streaming»La FastAPI generada admite respuestas de streaming de forma predeterminada cuando se usa una API REST. La infraestructura está configurada para usar el AWS Lambda Web Adapter para ejecutar tu FastAPI a través de uvicorn dentro de Lambda, con ResponseTransferMode.STREAM en API Gateway para todas las operaciones de API REST, lo que permite que el streaming funcione junto con operaciones que no son de streaming.
Usar JsonStreamingResponse
Sección titulada «Usar JsonStreamingResponse»El init.py generado exporta una clase JsonStreamingResponse que proporciona streaming con seguridad de tipos y generación adecuada de esquema OpenAPI. Esto asegura que el generador connection pueda producir métodos de cliente de streaming correctamente tipados.
from pydantic import BaseModelfrom .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())La clase JsonStreamingResponse:
- Serializa modelos Pydantic al formato JSON Lines (
application/jsonl) - Proporciona un helper
openapi_responseque genera el esquema OpenAPI correcto conitemSchema, permitiendo que el generadorconnectionproduzca métodos de cliente de streaming con seguridad de tipos
Consumo
Sección titulada «Consumo»Para consumir un flujo de respuestas, puedes hacer uso del generador connection que proporcionará un método con seguridad de tipos para iterar sobre tus fragmentos transmitidos.
Desplegar tu FastAPI
Sección titulada «Desplegar tu FastAPI»El generador de FastAPI crea infraestructura como código CDK o Terraform basada en tu iac seleccionado. Puedes usar esto para desplegar tu FastAPI.
El constructo CDK para desplegar tu API en la carpeta common/constructs. Puedes usar esto en una aplicación CDK:
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(), }); }}Esto configura:
- Una función AWS Lambda para cada operación en la aplicación FastAPI
- API Gateway HTTP/REST API como disparador de la función
- Roles y permisos de IAM
- Grupo de logs de CloudWatch
- Configuración de trazado de X-Ray
- Espacio de nombres de métricas de CloudWatch
Los módulos de Terraform para desplegar tu API están en la carpeta common/terraform. Puedes usar esto en una configuración de Terraform.
El módulo de API almacena su zip de despliegue de Lambda en un bucket S3 de activos compartido — consulta la guía de infraestructura de Terraform para más detalles. Instancia el módulo core/asset-bucket una vez por despliegue y pasa su salida bucket_name a cada módulo de API / Lambda a través de la entrada asset_bucket_name:
module "asset_bucket" { source = "../../common/terraform/src/core/asset-bucket"}
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Environment variables for the Lambda function env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Additional IAM policies if needed additional_iam_policy_statements = [ # Add any additional permissions your API needs ]
tags = local.common_tags}Esto configura:
- Una función AWS Lambda que sirve todas las rutas de FastAPI
- API Gateway HTTP/REST API como disparador de la función
- Roles y permisos de IAM
- Grupo de logs de CloudWatch
- Configuración de trazado de X-Ray
- Configuración de CORS
El módulo de Terraform proporciona varias salidas que puedes usar:
# Access the API endpointoutput "api_url" { value = module.my_api.stage_invoke_url}
# Access Lambda function detailsoutput "lambda_function_name" { value = module.my_api.lambda_function_name}
# Access IAM role for granting additional permissionsoutput "lambda_execution_role_arn" { value = module.my_api.lambda_execution_role_arn}Puedes personalizar la configuración de CORS pasando variables al módulo:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Custom CORS configuration cors_allow_origins = ["https://myapp.com", "https://staging.myapp.com"] cors_allow_methods = ["GET", "POST", "PUT", "DELETE"] cors_allow_headers = [ "authorization", "content-type", "x-custom-header" ]
tags = local.common_tags}Para las API REST, el constructo generado asocia una Web ACL de AWS WAFv2 con la etapa de API Gateway de forma predeterminada. La Web ACL utiliza el conjunto de reglas predeterminado administrado por AWS (AWSManagedRulesCommonRuleSet y AWSManagedRulesKnownBadInputsRuleSet), proporcionando protección contra exploits web comunes, incluidos los OWASP Top 10. Los registros de solicitudes de WAF se escriben en un grupo de CloudWatch Logs.
Puedes editar el constructo rest-api generado para agregar, eliminar o ajustar reglas (por ejemplo, para agregar reglas basadas en tasa o grupos de reglas administradas adicionales).
Para optar por no participar (por ejemplo, para adjuntar tu propia Web ACL), establece enableWaf en false:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), enableWaf: false,});Para optar por no participar (por ejemplo, para adjuntar tu propia Web ACL), establece enable_waf en false:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name enable_waf = false}Registro de acceso
Sección titulada «Registro de acceso»Para las REST APIs, la infraestructura generada habilita el registro de acceso de forma predeterminada, escribiendo una línea JSON estructurada por solicitud en un grupo de CloudWatch Logs dedicado. El grupo de logs está cifrado con una clave KMS administrada por el cliente y se retiene durante un año.
API Gateway escribe los logs de acceso utilizando un rol de CloudWatch Logs a nivel de cuenta. Este rol se configura en la configuración AWS::ApiGateway::Account, que es un singleton por región por cuenta: solo hay un rol para cada REST API en la región. Para gestionar esto de forma segura en múltiples stacks implementados de forma independiente, la infraestructura generada:
- Crea un rol compartido de CloudWatch Logs y lo configura en la cuenta solo cuando no hay un rol funcional ya establecido, por lo que las implementaciones nunca sobrescriben un rol que pertenece a otro stack.
- Deja la configuración de la cuenta intacta durante el desmontaje, por lo que destruir un stack nunca deshabilita el registro para otras REST APIs en la región.
El rol de cuenta es gestionado por el constructo ApiGatewayAccount, un singleton con ámbito de stack resuelto mediante ApiGatewayAccount.ensure(scope). La etapa de cada REST API depende de él, y el rol se configura mediante un recurso personalizado respaldado por Lambda.
El formato del log de acceso es establecido por el constructo RestApi que tu API extiende. Para personalizarlo, pasa deployOptions a super en el archivo generado packages/common/constructs/src/app/apis/my-api.ts, manteniendo el tracingEnabled que el constructo ya establece:
super(scope, id, { apiName: 'MyApi', // ... deployOptions: { tracingEnabled: true, accessLogFormat: AccessLogFormat.clf(), }, ...props,});AccessLogFormat se importa desde aws-cdk-lib/aws-apigateway. Cualquier cosa que dejes sin configurar mantiene el valor predeterminado del constructo: un formato JSON con los campos estándar.
El rol de cuenta es gestionado por el módulo core/api/api-gateway-account, que es instanciado por el módulo de API generado. Configura la cuenta de forma idempotente y nunca se restablece con terraform destroy.
Puedes personalizar el formato del log de acceso editando el bloque access_log_settings en el recurso aws_api_gateway_stage en el módulo de API generado.
Integraciones
Sección titulada «Integraciones»Los constructos CDK de API REST/HTTP están configurados para proporcionar una interfaz con seguridad de tipos para definir integraciones para cada una de sus operaciones.
Los constructos CDK proporcionan soporte completo de integración con seguridad de tipos como se describe a continuación.
Integraciones Predeterminadas
Sección titulada «Integraciones Predeterminadas»Puede usar el método estático defaultIntegrations para hacer uso del patrón predeterminado, que define una función AWS Lambda individual para cada operación:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});Los módulos Terraform utilizan automáticamente el patrón de enrutador con una única función Lambda. No se necesita configuración adicional:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# The module automatically creates a single Lambda function # that handles all API operations tags = local.common_tags}Acceso a Integraciones
Sección titulada «Acceso a Integraciones»Puede acceder a las funciones AWS Lambda subyacentes a través de la propiedad integrations del constructo de API, de manera segura en cuanto a tipos. Por ejemplo, si su API define una operación llamada sayHello y necesita agregar algunos permisos a esta función, puede hacerlo de la siguiente manera:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
// sayHello is typed to the operations defined in your APIapi.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));Si su API utiliza el patrón shared, el enrutador Lambda compartido se expone como api.integrations.$router:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');Con el patrón de enrutador de Terraform, solo hay una función Lambda. Puede acceder a ella a través de las salidas del módulo:
# Grant additional permissions to the single Lambda functionresource "aws_iam_role_policy" "additional_permissions" { name = "additional-api-permissions" role = module.my_api.lambda_execution_role_name
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject" ] Resource = "arn:aws:s3:::my-bucket/*" } ] })}Personalización de Opciones Predeterminadas
Sección titulada «Personalización de Opciones Predeterminadas»Si desea personalizar las opciones utilizadas al crear la función Lambda para cada integración predeterminada, puede usar el método withDefaultOptions. Por ejemplo, si desea que todas sus funciones Lambda residan en una Vpc:
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});La configuración de VPC ya es compatible con el módulo generado — establezca enable_vpc junto con vpc_id y subnet_ids, y el módulo desplegará la función Lambda en su VPC detrás de un grupo de seguridad que crea para usted:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# VPC configuration enable_vpc = true vpc_id = aws_vpc.main.id subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id]
tags = local.common_tags}Para opciones que el módulo no expone, edite el recurso aws_lambda_function en el módulo Terraform generado directamente.
Personalización de Opciones por Operación
Sección titulada «Personalización de Opciones por Operación»Para personalizar las opciones utilizadas para crear la integración predeterminada para operaciones específicas (sin afectar a las demás), puede usar el método withOperationOptions. Por ejemplo, si desea aumentar el tiempo de espera de la función Lambda para solo una operación:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOperationOptions({ sayHello: { timeout: Duration.seconds(60), }, }) .build(),});
// The selected operations remain default integrations, so they're still typed accordingly:api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ ... }));Las opciones que especifique se fusionan con las opciones de integración predeterminadas (y cualquier opción establecida a través de withDefaultOptions). Tenga en cuenta que no puede especificar opciones para operaciones que haya reemplazado a través de withOverrides, ya que estas ya no usan la integración predeterminada.
Encontrará un error de tipo si la misma operación es objetivo tanto de withOperationOptions como de withOverrides, independientemente del orden en que los llame.
Para personalizar opciones para operaciones específicas con Terraform, necesita editar el módulo Terraform generado para configurar funciones Lambda individuales por operación (consulte la sección Integraciones Explícitas a continuación).
Anulación de Integraciones
Sección titulada «Anulación de Integraciones»También puede anular integraciones para operaciones específicas usando el método withOverrides. Cada anulación 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 seguro en cuanto a tipos. Por ejemplo, si desea anular una API getDocumentation para que apunte a documentación alojada por algún sitio web externo, podría lograrlo de la siguiente manera:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), }, }) .build(),});También notará que la integración anulada ya no tiene una propiedad handler al acceder a ella a través de api.integrations.getDocumentation.
Puede agregar propiedades adicionales a una integración que también se tipará en consecuencia, permitiendo que otros tipos de integración se abstraigan pero permanezcan seguros en cuanto a tipos, por ejemplo, si ha creado una integración S3 para una API REST y luego desea hacer referencia al bucket para una operación en particular, puede hacerlo de la siguiente manera:
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 mannerapi.integrations.getFile.bucket.grantRead(...);Anulación de Autorizadores
Sección titulada «Anulación de Autorizadores»También puede proporcionar options en su integración para anular opciones de método particulares como autorizadores, por ejemplo, si desea usar autenticación Cognito para su operación getDocumentation:
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(),});Integraciones Explícitas
Sección titulada «Integraciones Explícitas»Si lo prefiere, puede optar por no usar las integraciones predeterminadas y en su lugar proporcionar directamente una para cada operación. Esto es útil si, por ejemplo, cada operación necesita usar un tipo diferente de integración o si desea recibir un error de tipo al agregar nuevas operaciones:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});Para integraciones explícitas por operación con Terraform, debe modificar el módulo específico de la aplicación generado para reemplazar la integración proxy predeterminada con integraciones específicas para cada operación.
Edite packages/common/terraform/src/app/apis/my-api/my-api.tf:
- Elimine las rutas proxy predeterminadas (por ejemplo,
resource "aws_apigatewayv2_route" "proxy_routes") - Reemplace la única función Lambda con funciones individuales para cada operación
- Cree integraciones y rutas específicas para cada operación, reutilizando el mismo paquete ZIP:
# Remove the default single Lambda function resource "aws_lambda_function" "api_lambda" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... rest of configuration }
# Remove the default proxy integration resource "aws_apigatewayv2_integration" "lambda_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.api_lambda.invoke_arn # ... rest of configuration }
# Remove the default proxy routes resource "aws_apigatewayv2_route" "proxy_routes" { for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]) api_id = module.http_api.api_id route_key = "${each.key} /{proxy+}" target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" # ... rest of configuration }
# Add individual Lambda functions for each operation using the same bundle resource "aws_lambda_function" "say_hello_handler" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
# Add specific integrations for each operation resource "aws_apigatewayv2_integration" "say_hello_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.say_hello_handler.invoke_arn payload_format_version = "2.0" timeout_milliseconds = 30000 }
resource "aws_apigatewayv2_integration" "get_documentation_integration" { api_id = module.http_api.api_id integration_type = "HTTP_PROXY" integration_uri = "https://example.com/documentation" integration_method = "GET" }
# Add specific routes for each operation resource "aws_apigatewayv2_route" "say_hello_route" { api_id = module.http_api.api_id route_key = "POST /sayHello" target = "integrations/${aws_apigatewayv2_integration.say_hello_integration.id}" authorization_type = "AWS_IAM" }
resource "aws_apigatewayv2_route" "get_documentation_route" { api_id = module.http_api.api_id route_key = "GET /documentation" target = "integrations/${aws_apigatewayv2_integration.get_documentation_integration.id}" authorization_type = "NONE" }
# Add Lambda permissions for each function resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }# Remove the default single Lambda function resource "aws_lambda_function" "api_lambda" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApiHandler-${random_string.suffix.result}" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... rest of configuration }
# Remove the default proxy integration resource "aws_api_gateway_integration" "lambda_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.proxy_resource.id http_method = aws_api_gateway_method.proxy_method.http_method integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.api_lambda.invoke_arn # ... rest of configuration }
# Remove the default catch-all proxy method resource "aws_api_gateway_method" "proxy_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.proxy_resource.id http_method = "ANY" # ... rest of configuration }
# Add individual Lambda functions for each operation using the same bundle resource "aws_lambda_function" "say_hello_handler" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { s3_bucket = aws_s3_object.lambda_zip.bucket s3_key = aws_s3_object.lambda_zip.key s3_object_version = aws_s3_object.lambda_zip.version_id function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
# Add specific resources and methods for each operation resource "aws_api_gateway_resource" "say_hello_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "sayHello" }
resource "aws_api_gateway_method" "say_hello_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = "POST" authorization = "AWS_IAM" }
resource "aws_api_gateway_integration" "say_hello_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = aws_api_gateway_method.say_hello_method.http_method
integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.say_hello_handler.invoke_arn }
resource "aws_api_gateway_resource" "get_documentation_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "documentation" }
resource "aws_api_gateway_method" "get_documentation_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = "GET" authorization = "NONE" }
resource "aws_api_gateway_integration" "get_documentation_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = aws_api_gateway_method.get_documentation_method.http_method
integration_http_method = "GET" type = "HTTP" uri = "https://example.com/documentation" }
# Update deployment to depend on new integrations~ resource "aws_api_gateway_deployment" "api_deployment" { rest_api_id = module.rest_api.api_id
depends_on = [ aws_api_gateway_integration.lambda_integration, aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ]
lifecycle { create_before_destroy = true }
triggers = { redeployment = sha1(jsonencode([ aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ])) } }
# Add Lambda permissions for each function resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }Patrón de Integración
Sección titulada «Patrón de Integración»Los constructos de API CDK generados admiten dos patrones de integración:
isolatedcrea una función Lambda por operación. Este es el valor predeterminado para las API generadas.sharedcrea un único enrutador Lambda predeterminado y lo reutiliza para cada operación a menos que anule integraciones específicas.
isolated le brinda permisos y configuración más detallados por operación. shared reduce la proliferación de Lambda y la integración de API Gateway mientras permite anulaciones selectivas.
Por ejemplo, establecer pattern en 'shared' crea una única función en lugar de una por integración:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { ... return IntegrationBuilder.rest({ pattern: 'shared', ... }); };}Los módulos Terraform utilizan automáticamente el patrón de enrutador - este es el enfoque predeterminado y el único compatible. El módulo generado crea una única función Lambda que maneja todas las operaciones de API.
Simplemente puede instanciar el módulo predeterminado para obtener el patrón de enrutador:
# Default router pattern - single Lambda function for all operationsmodule "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Single Lambda function handles all operations automatically tags = local.common_tags}Generación de Código
Sección titulada «Generación de Código»Dado que las operaciones en FastAPI se definen en Python y la infraestructura CDK en TypeScript, instrumentamos la generación de código para proporcionar metadatos al constructo CDK y proporcionar una interfaz con seguridad de tipos para las integraciones.
Se agrega un objetivo generate:<ApiName>-metadata al project.json de los constructos comunes para facilitar esta generación de código, que emite un archivo como packages/common/constructs/src/generated/my-api/metadata.gen.ts. Dado que esto se genera en tiempo de compilación, se ignora en el control de versiones.
Conceder Acceso (Solo IAM)
Sección titulada «Conceder Acceso (Solo IAM)»Si seleccionaste usar autenticación IAM, puedes usar el método grantInvokeAccess para conceder acceso a tu API:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);# Create an IAM policy to allow invoking the APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Policy to allow invoking the FastAPI"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Attach the policy to an IAM role (e.g., for authenticated users)resource "aws_iam_role_policy_attachment" "api_invoke_access" { role = aws_iam_role.authenticated_user_role.name policy_arn = aws_iam_policy.api_invoke_policy.arn}
# Or attach to an existing role by nameresource "aws_iam_role_policy_attachment" "api_invoke_access_existing" { role = "MyExistingRole" policy_arn = aws_iam_policy.api_invoke_policy.arn}Las salidas clave del módulo de API que puedes usar para políticas de IAM son:
module.my_api.api_execution_arn- Para conceder permisos execute-api:Invokemodule.my_api.api_arn- El ARN de API Gatewaymodule.my_api.lambda_function_arn- El ARN de la función Lambda
Desarrollo Local
Sección titulada «Desarrollo Local»El generador configura un servidor de desarrollo local que puedes ejecutar con:
pnpm nx serve my-apiyarn nx serve my-apinpx nx serve my-apibunx nx serve my-apiEsto inicia un servidor de desarrollo local de FastAPI con:
- Recarga automática en cambios de código
- Documentación interactiva de la API en
/docso/redoc - Esquema OpenAPI en
/openapi.json
Invocar tu FastAPI
Sección titulada «Invocar tu FastAPI»Para invocar tu API desde un sitio web React, puedes usar el generador connection.
Conexiones
Sección titulada «Conexiones»Usa el generador connection para integrar este proyecto con otros en tu espacio de trabajo. Las siguientes conexiones involucran este proyecto: