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.
Generar una API FastAPI
Sección titulada «Generar una API FastAPI»Puedes generar una nueva API FastAPI de dos formas:
- 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#fast-api
- Complete los parámetros requeridos
- Haga clic en
Generate
pnpm nx g @aws/nx-plugin:py#fast-api
yarn nx g @aws/nx-plugin:py#fast-api
npx nx g @aws/nx-plugin:py#fast-api
bunx nx g @aws/nx-plugin:py#fast-api
También puede realizar una ejecución en seco para ver qué archivos se cambiarían
pnpm nx g @aws/nx-plugin:py#fast-api --dry-run
yarn nx g @aws/nx-plugin:py#fast-api --dry-run
npx nx g @aws/nx-plugin:py#fast-api --dry-run
bunx nx g @aws/nx-plugin:py#fast-api --dry-run
Opciones
Sección titulada «Opciones»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 |
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 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
Infraestructura
Sección titulada «Infraestructura»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
Directorypackages/common/terraform
Directorysrc
Directoryapp/ Módulos de Terraform para infraestructura específica de un proyecto/generador
- …
Directorycore/ Módulos genéricos reutilizados por los módulos en
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
Directorypackages/common/terraform/src
Directoryapp
Directoryapis
Directory<project-name>
- <project-name>.tf Módulo para implementar tu API
Directorycore
Directoryapi
Directoryhttp-api
- http-api.tf Módulo para implementar una API HTTP (si seleccionaste implementar una API HTTP)
Directoryrest-api
- rest-api.tf Módulo para implementar una API REST (si seleccionaste implementar una API REST)
Implementando tu FastAPI
Sección titulada «Implementando tu FastAPI»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, tracerfrom 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:
- Integración de AWS Lambda Powertools para observabilidad
- Middleware de manejo de errores
- Correlación de solicitudes/respuestas
- Recolección de métricas
- Manejador de AWS Lambda usando Mangum
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 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
Trazado
Sección titulada «Trazado»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_methoddef 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}
Métricas
Sección titulada «Métricas»Las métricas de CloudWatch se recogen automáticamente para cada solicitud. Puedes añadir 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 por defecto incluyen:
- Conteo de solicitudes
- Conteos de éxito/fallo
- Métricas de cold start
- Métricas por ruta
Manejo de errores
Sección titulada «Manejo de errores»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:
- Registran la excepción completa con stack trace
- Registran una métrica de fallo
- Devuelven una respuesta segura 500 al cliente
- Preservan el ID de correlación
Streaming
Sección titulada «Streaming»Con FastAPI, puedes transmitir una respuesta al cliente usando el tipo de respuesta StreamingResponse
.
Cambios en infraestructura
Sección titulada «Cambios en infraestructura»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', }, }, }); }}
Para esto con Terraform, puedes reemplazar la infraestructura de API Gateway generada por una Lambda Function URL que soporte streaming.
Ejemplo de Configuración Terraform para Function URL
# Data sources para contexto AWS actualdata "aws_caller_identity" "current" {}data "aws_region" "current" {}
# Función Lambda para FastAPI con streamingresource "aws_lambda_function" "my_api_handler" { filename = "../../../../../../dist/packages/my_api/bundle.zip" function_name = "my-api-handler" role = aws_iam_role.lambda_execution_role.arn handler = "run.sh" runtime = "python3.12" timeout = 30 source_code_hash = filebase64sha256("../../../../../../dist/packages/my_api/bundle.zip")
# Habilitar X-Ray tracing tracing_config { mode = "Active" }
# Variables de entorno para Lambda Web Adapter environment { variables = { AWS_CONNECTION_REUSE_ENABLED = "1" PORT = "8000" AWS_LWA_INVOKE_MODE = "response_stream" AWS_LAMBDA_EXEC_WRAPPER = "/opt/bootstrap" } }
# Añadir capa Lambda Web Adapter layers = [ "arn:aws:lambda:${data.aws_region.current.name}:753240598075:layer:LambdaAdapterLayerX86:24" ]
depends_on = [ aws_iam_role_policy_attachment.lambda_logs, aws_cloudwatch_log_group.lambda_logs, ]}
# Grupo de logs CloudWatch para Lambdaresource "aws_cloudwatch_log_group" "lambda_logs" { name = "/aws/lambda/my-api-handler" retention_in_days = 14}
# Rol IAM para ejecución de Lambdaresource "aws_iam_role" "lambda_execution_role" { name = "my-api-lambda-execution-role"
assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } } ] })}
# Adjuntar política básica de ejecuciónresource "aws_iam_role_policy_attachment" "lambda_logs" { role = aws_iam_role.lambda_execution_role.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"}
# Adjuntar política de X-Rayresource "aws_iam_role_policy_attachment" "lambda_xray" { role = aws_iam_role.lambda_execution_role.name policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"}
# Lambda Function URL con soporte para streamingresource "aws_lambda_function_url" "my_api_url" { function_name = aws_lambda_function.my_api_handler.function_name authorization_type = "AWS_IAM" invoke_mode = "RESPONSE_STREAM"
cors { allow_credentials = false allow_origins = ["*"] allow_methods = ["*"] allow_headers = [ "authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token" ] expose_headers = ["date", "keep-alive"] max_age = 86400 }}
# Output de la Function URLoutput "my_api_url" { description = "URL para FastAPI con streaming" value = aws_lambda_function_url.my_api_url.function_url}
# Opcional: Crear parámetro SSM para configuraciónresource "aws_ssm_parameter" "my_api_url" { name = "/runtime-config/apis/MyApi" type = "String" value = aws_lambda_function_url.my_api_url.function_url
tags = { Environment = "production" Service = "my-api" }}
# Política IAM para conceder acceso de invocaciónresource "aws_iam_policy" "my_api_invoke_policy" { name = "my-api-invoke-policy" description = "Política para permitir invocar la Function URL"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "lambda:InvokeFunctionUrl" Resource = aws_lambda_function.my_api_handler.arn Condition = { StringEquals = { "lambda:FunctionUrlAuthType" = "AWS_IAM" } } } ] })}
# Ejemplo: Adjuntar política a un rol (descomentar y modificar)# resource "aws_iam_role_policy_attachment" "my_api_invoke_access" {# role = var.authenticated_role_name# policy_arn = aws_iam_policy.my_api_invoke_policy.arn# }
Implementación
Sección titulada «Implementación»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 BaseModelfrom 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")
Consumo
Sección titulada «Consumo»Para consumir streams, puedes usar el Generador de Conexión API que provee métodos tipados para iterar chunks.
Desplegando tu FastAPI
Sección titulada «Desplegando tu FastAPI»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:
- Función AWS Lambda para cada operación
- API Gateway HTTP/REST API como trigger
- Roles y permisos IAM
- Grupo de logs CloudWatch
- Configuración de trazado X-Ray
- Namespace de métricas CloudWatch
Los módulos Terraform están en common/terraform
. Puedes usarlos en tu configuración:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Variables de entorno para Lambda env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Políticas IAM adicionales additional_iam_policy_statements = [ # Permisos adicionales para tu API ]
tags = local.common_tags}
Esto configura:
- Función Lambda que sirve todas las rutas
- API Gateway HTTP/REST API como trigger
- Roles y permisos IAM
- Grupo de logs CloudWatch
- Trazado X-Ray
- Configuración CORS
Salidas del módulo Terraform:
# URL de la APIoutput "api_url" { value = module.my_api.stage_invoke_url}
# Detalles de la función Lambdaoutput "lambda_function_name" { value = module.my_api.lambda_function_name}
# Rol IAM para permisosoutput "lambda_execution_role_arn" { value = module.my_api.lambda_execution_role_arn}
Personaliza CORS con variables:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Configuración CORS personalizada 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}
Integraciones
Sección titulada «Integraciones»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.
Los módulos de Terraform usan el “patrón router” con una única función Lambda que maneja todas las operaciones. No se admiten integraciones type-safe - el módulo crea una función Lambda que maneja todas las solicitudes de la API.
Para integraciones explícitas por operación con Terraform, necesitarías crear manualmente funciones Lambda individuales y rutas de API Gateway. Consulta la sección Integraciones Explícitas para ver ejemplos.
Integraciones por defecto
Sección titulada «Integraciones por defecto»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(),});
Los módulos de Terraform usan automáticamente el patrón router con una única función Lambda. No se necesita configuración adicional:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# El módulo crea automáticamente una única función Lambda # que maneja todas las operaciones de la API tags = local.common_tags}
Accediendo a las integraciones
Sección titulada «Accediendo a las integraciones»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 APIapi.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));
Con el patrón router de Terraform, solo hay una función Lambda. Puedes acceder a ella a través de los outputs del módulo:
# Otorgar permisos adicionales a la única función Lambdaresource "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/*" } ] })}
Personalizando opciones por defecto
Sección titulada «Personalizando opciones por defecto»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(),});
Para personalizar opciones como configuración de VPC, necesitas editar el módulo de Terraform generado. Por ejemplo, para agregar soporte de VPC a todas las funciones Lambda:
# Agregar variables de VPCvariable "vpc_subnet_ids" { description = "Lista de IDs de subredes VPC para la función Lambda" type = list(string) default = []}
variable "vpc_security_group_ids" { description = "Lista de IDs de grupos de seguridad VPC para la función Lambda" type = list(string) default = []}
# Actualizar el recurso de función Lambdaresource "aws_lambda_function" "api_lambda" { # ... configuración existente ...
# Agregar configuración VPC vpc_config { subnet_ids = var.vpc_subnet_ids security_group_ids = var.vpc_security_group_ids }}
Luego usar el módulo con configuración VPC:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Configuración VPC vpc_subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id] vpc_security_group_ids = [aws_security_group.lambda_sg.id]
tags = local.common_tags}
Sobrescribiendo integraciones
Sección titulada «Sobrescribiendo integraciones»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-safeapi.integrations.getFile.bucket.grantRead(...);
No se admite sobrescribir integraciones específicas con módulos de Terraform debido al patrón router. Todas las operaciones son manejadas por una única función Lambda.
Para diferentes tipos de integración por operación, necesitarías implementar integraciones explícitas manualmente (ver sección Integraciones Explícitas).
Sobrescribiendo autorizadores
Sección titulada «Sobrescribiendo autorizadores»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(),});
No se admiten sobrescritas de autorizadores por operación con módulos de Terraform. Toda la API usa el método de autenticación especificado al generar la API (IAM, Cognito o None).
Para autorización por operación, necesitarías implementar integraciones explícitas manualmente como se muestra abajo.
Integraciones explícitas
Sección titulada «Integraciones explícitas»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(...), }, },});
Para integraciones explícitas por operación con Terraform, debes modificar el módulo específico de la aplicación generado para reemplazar la integración proxy por defecto con integraciones específicas para cada operación.
Edita packages/common/terraform/src/app/apis/my-api/my-api.tf
:
- Eliminar las rutas proxy por defecto (ej.
resource "aws_apigatewayv2_route" "proxy_routes"
) - Reemplazar la única función Lambda con funciones individuales para cada operación
- Crear integraciones y rutas específicas para cada operación, reusando el mismo paquete ZIP:
# Eliminar la función Lambda única por defecto resource "aws_lambda_function" "api_lambda" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... resto de la configuración }
# Eliminar la integración proxy por defecto 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 # ... resto de la configuración }
# Eliminar las rutas proxy por defecto 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}" # ... resto de la configuración }
# Agregar funciones Lambda individuales para cada operación usando el mismo bundle resource "aws_lambda_function" "say_hello_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Handler específico para esta operación runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Handler específico para esta operación runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
# Agregar integraciones específicas para cada operación 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" }
# Agregar rutas específicas para cada operación 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" }
# Agregar permisos Lambda para cada función 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}/*/*" }
# Eliminar la función Lambda única por defecto resource "aws_lambda_function" "api_lambda" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... resto de la configuración }
# Eliminar la integración proxy por defecto 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 # ... resto de la configuración }
# Eliminar las rutas proxy por defecto 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}" # ... resto de la configuración }
# Agregar funciones Lambda individuales para cada operación usando el mismo bundle resource "aws_lambda_function" "say_hello_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Handler específico para esta operación runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Handler específico para esta operación runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
# Agregar recursos y métodos específicos para cada operación 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" }
# Actualizar deployment para depender de nuevas integraciones~ 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, ])) } }
# Agregar permisos Lambda para cada función 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 Router
Sección titulada «Patrón Router»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:
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.
Los módulos de Terraform usan automáticamente el patrón router - este es el enfoque por defecto y único soportado. El módulo generado crea una única función Lambda que maneja todas las operaciones de la API.
Simplemente instancia el módulo por defecto para obtener el patrón router:
# Patrón router por defecto - única función Lambda para todas las operacionesmodule "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Una única función Lambda maneja todas las operaciones automáticamente tags = local.common_tags}
Generación de código
Sección titulada «Generación de código»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.
Concediendo acceso (solo IAM)
Sección titulada «Concediendo acceso (solo IAM)»Si usas autenticación IAM
, puedes usar grantInvokeAccess
para conceder acceso:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);
# Política IAM para invocar la APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Política para invocar FastAPI"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Adjuntar política a un rol IAMresource "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}
# O adjuntar a un rol existenteresource "aws_iam_role_policy_attachment" "api_invoke_access_existing" { role = "MyExistingRole" policy_arn = aws_iam_policy.api_invoke_policy.arn}
Salidas clave del módulo:
module.my_api.api_execution_arn
- Para permisos execute-api:Invokemodule.my_api.api_arn
- ARN de API Gatewaymodule.my_api.lambda_function_arn
- 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 run my-api:serve
yarn nx run my-api:serve
npx nx run my-api:serve
bunx 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
Invocando tu FastAPI
Sección titulada «Invocando tu FastAPI»Para invocar tu API desde un sitio React, usa el generador api-connection
.