Smithy TypeScript API
Smithy es un lenguaje de definición de interfaces independiente del protocolo para crear APIs de manera modelada.
El generador de API TypeScript para Smithy crea una nueva API usando Smithy para la definición del servicio y el SDK de servidor TypeScript para Smithy para la implementación. El generador provee infraestructura como código con CDK o Terraform para desplegar tu servicio en AWS Lambda, expuesto a través de una API REST de AWS API Gateway. Ofrece desarrollo de APIs con seguridad de tipos mediante generación automática de código a partir de modelos Smithy. El manejador generado utiliza AWS Lambda Powertools para TypeScript para observabilidad, incluyendo registro, trazado con AWS X-Ray y métricas de CloudWatch.
Generar una API TypeScript con Smithy
Sección titulada «Generar una API TypeScript con Smithy»Puedes generar una nueva API TypeScript con Smithy 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 - ts#smithy-api
- Complete los parámetros requeridos
- Haga clic en
Generate
pnpm nx g @aws/nx-plugin:ts#smithy-api
yarn nx g @aws/nx-plugin:ts#smithy-api
npx nx g @aws/nx-plugin:ts#smithy-api
bunx nx g @aws/nx-plugin:ts#smithy-api
También puede realizar una ejecución en seco para ver qué archivos se cambiarían
pnpm nx g @aws/nx-plugin:ts#smithy-api --dry-run
yarn nx g @aws/nx-plugin:ts#smithy-api --dry-run
npx nx g @aws/nx-plugin:ts#smithy-api --dry-run
bunx nx g @aws/nx-plugin:ts#smithy-api --dry-run
Opciones
Sección titulada «Opciones»Parámetro | Tipo | Predeterminado | Descripción |
---|---|---|---|
name Requerido | string | - | The name of the API (required). Used to generate class names and file paths. |
namespace | string | - | The namespace for the Smithy API. Defaults to your monorepo scope |
computeType | string | ServerlessApiGatewayRestApi | The type of compute to use to deploy this API. |
auth | string | IAM | The method used to authenticate with your API. Choose between IAM (default), Cognito or None. |
directory | string | packages | The directory to store the application in. |
iacProvider | string | Inherit | The preferred IaC provider. By default this is inherited from your initial selection. |
Salida del Generador
Sección titulada «Salida del Generador»El generador crea dos proyectos relacionados en el directorio <directory>/<api-name>
:
Directorymodel/ Proyecto del modelo Smithy
- project.json Configuración del proyecto y objetivos de build
- smithy-build.json Configuración de build para Smithy
- build.Dockerfile Configuración de Docker para construir artefactos Smithy
Directorysrc/
- main.smithy Definición principal del servicio
Directoryoperations/
- echo.smithy Definición de operación de ejemplo
Directorybackend/ Implementación del backend en TypeScript
- project.json Configuración del proyecto y objetivos de build
- rolldown.config.ts Configuración de bundling
Directorysrc/
- handler.ts Manejador de AWS Lambda
- local-server.ts Servidor local para desarrollo
- service.ts Implementación del servicio
- context.ts Definición del contexto del servicio
Directoryoperations/
- echo.ts Implementación de operación de ejemplo
Directorygenerated/ SDK TypeScript generado (creado durante el build)
- …
Infraestructura
Sección titulada «Infraestructura»Dado que este generador crea infraestructura como código según el iacProvider
seleccionado, generará un proyecto en packages/common
que incluye los constructos CDK o módulos Terraform relevantes.
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
Directoryapis/
- <project-name>.ts Constructo CDK para desplegar tu API
Directorycore/ Constructos genéricos reutilizables por los de
app
Directoryapi/
- rest-api.ts Constructo CDK para desplegar una API REST
- utils.ts Utilidades para los constructos de API
- index.ts Punto de entrada que exporta los constructos de
app
- project.json Objetivos de build y configuración del proyecto
Directorypackages/common/terraform
Directorysrc
Directoryapp/ Módulos Terraform para infraestructura específica de un proyecto/generador
Directoryapis/
Directory<project-name>/
- <project-name>.tf Módulo para desplegar tu API
Directorycore/ Módulos genéricos reutilizados por los de
app
Directoryapi/
Directoryrest-api/
- rest-api.tf Módulo para desplegar una API REST
- project.json Objetivos de build y configuración del proyecto
Implementando tu API Smithy
Sección titulada «Implementando tu API Smithy»Definiendo Operaciones en Smithy
Sección titulada «Definiendo Operaciones en Smithy»Las operaciones se definen en archivos Smithy dentro del proyecto del modelo. La definición principal del servicio está en main.smithy
:
$version: "2.0"
namespace your.namespace
use aws.protocols#restJson1use smithy.framework#ValidationException
@title("YourService")@restJson1service YourService { version: "1.0.0" operations: [ Echo, // Añade tus operaciones aquí ] errors: [ ValidationException ]}
Las operaciones individuales se definen en archivos separados en el directorio operations/
:
$version: "2.0"
namespace your.namespace
@http(method: "POST", uri: "/echo")operation Echo { input: EchoInput output: EchoOutput}
structure EchoInput { @required message: String
foo: Integer bar: String}
structure EchoOutput { @required message: String}
Implementando Operaciones en TypeScript
Sección titulada «Implementando Operaciones en TypeScript»Las implementaciones de operaciones se encuentran en el directorio src/operations/
del proyecto backend. Cada operación se implementa usando los tipos generados del SDK de servidor TypeScript (generados en tiempo de build a partir de tu modelo Smithy).
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input) => { // Tu lógica de negocio aquí return { message: `Echo: ${input.message}` // seguridad de tipos basada en tu modelo Smithy };};
Las operaciones deben registrarse en la definición del servicio en src/service.ts
:
import { ServiceContext } from './context.js';import { YourServiceService } from './generated/ssdk/index.js';import { Echo } from './operations/echo.js';// Importa otras operaciones aquí
// Registra las operaciones en el servicio aquíexport const Service: YourServiceService<ServiceContext> = { Echo, // Añade otras operaciones aquí};
Contexto del Servicio
Sección titulada «Contexto del Servicio»Puedes definir un contexto compartido para tus operaciones en context.ts
:
export interface ServiceContext { // Tracer, logger y metrics de Powertools se proveen por defecto tracer: Tracer; logger: Logger; metrics: Metrics; // Añade dependencias compartidas, conexiones a bases de datos, etc. dbClient: any; userIdentity: string;}
Este contexto se pasa a todas las implementaciones de operaciones y puede usarse para compartir recursos como conexiones a bases de datos, configuración o utilidades de registro.
Observabilidad con AWS Lambda Powertools
Sección titulada «Observabilidad con AWS Lambda Powertools»Registros
Sección titulada «Registros»El generador configura registro estructurado usando AWS Lambda Powertools con inyección automática de contexto mediante middleware Middy.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
Puedes acceder al logger desde tus implementaciones de operaciones vía el contexto:
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { ctx.logger.info('Tu mensaje de log'); // ...};
Trazado
Sección titulada «Trazado»El trazado con AWS X-Ray se configura automáticamente mediante el middleware captureLambdaHandler
.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
Puedes añadir subsegmentos personalizados a tus trazas en tus operaciones:
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { // Crea un nuevo subsegmento const subsegment = ctx.tracer.getSegment()?.addNewSubsegment('operacion-personalizada'); try { // Tu lógica aquí } catch (error) { subsegment?.addError(error as Error); throw error; } finally { subsegment?.close(); }};
Métricas
Sección titulada «Métricas»Las métricas de CloudWatch se recopilan automáticamente para cada solicitud mediante el middleware logMetrics
.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
Puedes añadir métricas personalizadas en tus operaciones:
import { MetricUnit } from '@aws-lambda-powertools/metrics';import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { ctx.metrics.addMetric("MetricaPersonalizada", MetricUnit.Count, 1); // ...};
Manejo de Errores
Sección titulada «Manejo de Errores»Smithy provee manejo de errores integrado. Puedes definir errores personalizados en tu modelo Smithy:
@error("client")@httpError(400)structure InvalidRequestError { @required message: String}
Y registrarlos en tu operación/servicio:
operation MyOperation { ... errors: [InvalidRequestError]}
Luego lanzarlos en tu implementación TypeScript:
import { InvalidRequestError } from '../generated/ssdk/index.js';
export const MyOperation: MyOperationHandler<ServiceContext> = async (input) => { if (!input.requiredField) { throw new InvalidRequestError({ message: "Falta el campo requerido" }); }
return { /* respuesta exitosa */ };};
Construcción y Generación de Código
Sección titulada «Construcción y Generación de Código»El proyecto del modelo Smithy usa Docker para construir los artefactos Smithy y generar el SDK de servidor TypeScript:
pnpm nx run <model-project>:build
yarn nx run <model-project>:build
npx nx run <model-project>:build
bunx nx run <model-project>:build
Este proceso:
- Compila el modelo Smithy y lo valida
- Genera la especificación OpenAPI a partir del modelo Smithy
- Crea el SDK de servidor TypeScript con interfaces de operación con seguridad de tipos
- Genera artefactos de build en
dist/<model-project>/build/
El proyecto backend copia automáticamente el SDK generado durante la compilación:
pnpm nx run <backend-project>:copy-ssdk
yarn nx run <backend-project>:copy-ssdk
npx nx run <backend-project>:copy-ssdk
bunx nx run <backend-project>:copy-ssdk
Objetivo de Bundle
Sección titulada «Objetivo de Bundle»El generador configura automáticamente un objetivo bundle
que utiliza Rolldown para crear un paquete de despliegue:
pnpm nx run <project-name>:bundle
yarn nx run <project-name>:bundle
npx nx run <project-name>:bundle
bunx nx run <project-name>:bundle
La configuración de Rolldown se encuentra en rolldown.config.ts
, con una entrada por cada bundle a generar. Rolldown gestiona la creación de múltiples bundles en paralelo si están definidos.
Desarrollo Local
Sección titulada «Desarrollo Local»El generador configura un servidor local con recarga en caliente:
pnpm nx run <backend-project>:serve
yarn nx run <backend-project>:serve
npx nx run <backend-project>:serve
bunx nx run <backend-project>:serve
Desplegando tu API Smithy
Sección titulada «Desplegando tu API Smithy»El generador crea infraestructura como código con CDK o Terraform según el iacProvider
seleccionado.
El constructo CDK para desplegar tu API está en la carpeta common/constructs
:
import { MyApi } from ':my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // Añade la API a tu stack const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}
Esto configura:
- Una función AWS Lambda para el servicio Smithy
- API Gateway REST API como disparador de la función
- Roles y permisos IAM
- Grupo de logs de CloudWatch
- Configuración de trazado X-Ray
Los módulos Terraform para desplegar tu API están en la carpeta common/terraform
:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Variables de entorno para la función Lambda env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Políticas IAM adicionales si son necesarias additional_iam_policy_statements = [ # Añade permisos adicionales que necesite tu API ]
tags = local.common_tags}
Esto configura:
- Una función AWS Lambda que sirve la API Smithy
- API Gateway REST API como disparador de la función
- Roles y permisos IAM
- Grupo de logs de CloudWatch
- Configuración de trazado X-Ray
- Configuración CORS
El módulo Terraform provee varias salidas:
# Accede al endpoint de la APIoutput "api_url" { value = module.my_api.stage_invoke_url}
# Accede a detalles de la función Lambdaoutput "lambda_function_name" { value = module.my_api.lambda_function_name}
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»Dado que las operaciones se definen en Smithy, usamos generación de código para proveer metadatos al constructo CDK para integraciones con seguridad de tipos.
Se añade un objetivo generate:<ApiName>-metadata
al project.json
de los constructos comunes para facilitar esta generación, el cual emite un archivo como packages/common/constructs/src/generated/my-api/metadata.gen.ts
. Como esto se genera en tiempo de build, se ignora en control de versiones.
Otorgando Acceso (Solo IAM)
Sección titulada «Otorgando Acceso (Solo IAM)»Si seleccionaste autenticación IAM
, puedes usar el método grantInvokeAccess
para otorgar acceso a tu API:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);
# Crea una política IAM para permitir invocar la APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Política para permitir invocar la API Smithy"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Adjunta la 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}
Invocando tu API Smithy
Sección titulada «Invocando tu API Smithy»Para invocar tu API desde un sitio web React, puedes usar el generador api-connection
, que provee generación de cliente con seguridad de tipos a partir de tu modelo Smithy.