API TypeScript do Smithy
Smithy é uma linguagem de definição de interface independente de protocolo para criação de APIs de forma orientada a modelos.
O gerador de API Smithy TypeScript cria uma nova API usando Smithy para definição de serviços e o Smithy TypeScript Server SDK para implementação. O gerador fornece infraestrutura como código via CDK ou Terraform para implantar seu serviço na AWS Lambda, exposto através de uma API REST do AWS API Gateway. Oferece desenvolvimento de API com tipagem segura e geração automática de código a partir de modelos Smithy. O handler gerado utiliza AWS Lambda Powertools for TypeScript para observabilidade, incluindo logging, rastreamento com AWS X-Ray e métricas no CloudWatch.
Gerar uma API Smithy TypeScript
Seção intitulada “Gerar uma API Smithy TypeScript”Você pode gerar uma nova API Smithy TypeScript de duas formas:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#smithy-api
- Preencha os parâmetros obrigatórios
- Clique em
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
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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
Parâmetro | Tipo | Padrão | Descrição |
---|---|---|---|
name Obrigatório | 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. |
Saída do Gerador
Seção intitulada “Saída do Gerador”O gerador cria dois projetos relacionados no diretório <directory>/<api-name>
:
Directorymodel/ Projeto de modelo Smithy
- project.json Configuração do projeto e targets de build
- smithy-build.json Configuração de build Smithy
- build.Dockerfile Configuração Docker para construir artefatos Smithy
Directorysrc/
- main.smithy Definição principal do serviço
Directoryoperations/
- echo.smithy Exemplo de definição de operação
Directorybackend/ Implementação TypeScript do backend
- project.json Configuração do projeto e targets de build
- rolldown.config.ts Configuração de bundle
Directorysrc/
- handler.ts Handler AWS Lambda
- local-server.ts Servidor local de desenvolvimento
- service.ts Implementação do serviço
- context.ts Definição de contexto do serviço
Directoryoperations/
- echo.ts Exemplo de implementação de operação
Directorygenerated/ SDK TypeScript gerado (criado durante o build)
- …
Infraestrutura
Seção intitulada “Infraestrutura”Como este gerador cria infraestrutura como código baseada no iacProvider
escolhido, ele criará um projeto em packages/common
que inclui os constructs CDK ou módulos Terraform relevantes.
O projeto comum de infraestrutura como código é estruturado da seguinte forma:
Directorypackages/common/constructs
Directorysrc
Directoryapp/ Constructs para infraestrutura específica de um projeto/gerador
Directoryapis/
- <project-name>.ts Construct CDK para implantar sua API
Directorycore/ Constructs genéricos reutilizados por constructs em
app
Directoryapi/
- rest-api.ts Construct CDK para implantar uma API REST
- utils.ts Utilitários para os constructs de API
- index.ts Ponto de entrada exportando constructs de
app
- project.json Targets de build e configuração do projeto
Directorypackages/common/terraform
Directorysrc
Directoryapp/ Módulos Terraform para infraestrutura específica de um projeto/gerador
Directoryapis/
Directory<project-name>/
- <project-name>.tf Módulo para implantar sua API
Directorycore/ Módulos genéricos reutilizados por módulos em
app
Directoryapi/
Directoryrest-api/
- rest-api.tf Módulo para implantar uma API REST
- project.json Targets de build e configuração do projeto
Implementando sua API Smithy
Seção intitulada “Implementando sua API Smithy”Definindo Operações no Smithy
Seção intitulada “Definindo Operações no Smithy”Operações são definidas em arquivos Smithy dentro do projeto de modelo. A definição principal do serviço está em 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, // Adicione suas operações aqui ] errors: [ ValidationException ]}
Operações individuais são definidas em arquivos separados no diretório 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 Operações em TypeScript
Seção intitulada “Implementando Operações em TypeScript”As implementações das operações estão localizadas no diretório src/operations/
do projeto backend. Cada operação é implementada usando os tipos gerados do TypeScript Server SDK (gerados em tempo de build a partir do seu modelo Smithy).
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input) => { // Sua lógica de negócios aqui return { message: `Echo: ${input.message}` // tipagem segura baseada no seu modelo Smithy };};
As operações devem ser registradas na definição do serviço em src/service.ts
:
import { ServiceContext } from './context.js';import { YourServiceService } from './generated/ssdk/index.js';import { Echo } from './operations/echo.js';// Importe outras operações aqui
// Registre as operações no serviço aquiexport const Service: YourServiceService<ServiceContext> = { Echo, // Adicione outras operações aqui};
Contexto do Serviço
Seção intitulada “Contexto do Serviço”Você pode definir contexto compartilhado para suas operações em context.ts
:
export interface ServiceContext { // Tracer, logger e metrics do Powertools são fornecidos por padrão tracer: Tracer; logger: Logger; metrics: Metrics; // Adicione dependências compartilhadas, conexões de banco de dados, etc. dbClient: any; userIdentity: string;}
Este contexto é passado para todas as implementações de operações e pode ser usado para compartilhar recursos como conexões de banco de dados, configuração ou utilitários de logging.
Observabilidade com AWS Lambda Powertools
Seção intitulada “Observabilidade com AWS Lambda Powertools”Logging
Seção intitulada “Logging”O gerador configura logging estruturado usando AWS Lambda Powertools com injeção automática de contexto via middleware Middy.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
Você pode referenciar o logger das suas implementações de operações via 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('Sua mensagem de log'); // ...};
Tracing
Seção intitulada “Tracing”O rastreamento com AWS X-Ray é configurado automaticamente via middleware captureLambdaHandler
.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
Você pode adicionar subsegmentos personalizados aos seus traces nas operações:
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { // Cria um novo subsegmento const subsegment = ctx.tracer.getSegment()?.addNewSubsegment('operacao-personalizada'); try { // Sua lógica aqui } catch (error) { subsegment?.addError(error as Error); throw error; } finally { subsegment?.close(); }};
Métricas
Seção intitulada “Métricas”Métricas do CloudWatch são coletadas automaticamente para cada requisição via middleware logMetrics
.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
Você pode adicionar métricas personalizadas nas suas operações:
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); // ...};
Tratamento de Erros
Seção intitulada “Tratamento de Erros”Smithy fornece tratamento de erros embutido. Você pode definir erros personalizados no seu modelo Smithy:
@error("client")@httpError(400)structure InvalidRequestError { @required message: String}
E registrá-los na sua operação/serviço:
operation MyOperation { ... errors: [InvalidRequestError]}
Então lançá-los na implementação TypeScript:
import { InvalidRequestError } from '../generated/ssdk/index.js';
export const MyOperation: MyOperationHandler<ServiceContext> = async (input) => { if (!input.requiredField) { throw new InvalidRequestError({ message: "Campo obrigatório faltando" }); }
return { /* resposta de sucesso */ };};
Build e Geração de Código
Seção intitulada “Build e Geração de Código”O projeto de modelo Smithy usa Docker para construir os artefatos Smithy e gerar o TypeScript Server SDK:
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 processo:
- Compila o modelo Smithy e o valida
- Gera especificação OpenAPI a partir do modelo Smithy
- Cria TypeScript Server SDK com interfaces de operação tipadas
- Gera artefatos de build em
dist/<model-project>/build/
O projeto backend copia automaticamente o SDK gerado durante a compilação:
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
Target de Bundle
Seção intitulada “Target de Bundle”O gerador configura automaticamente um destino bundle
que utiliza o Rolldown para criar um pacote de implantação:
pnpm nx run <project-name>:bundle
yarn nx run <project-name>:bundle
npx nx run <project-name>:bundle
bunx nx run <project-name>:bundle
A configuração do Rolldown pode ser encontrada em rolldown.config.ts
, com uma entrada para cada pacote a ser gerado. O Rolldown gerencia a criação de múltiplos pacotes em paralelo, se definidos.
Desenvolvimento Local
Seção intitulada “Desenvolvimento Local”O gerador configura um servidor de desenvolvimento local com hot reloading:
pnpm nx run <backend-project>:serve
yarn nx run <backend-project>:serve
npx nx run <backend-project>:serve
bunx nx run <backend-project>:serve
Implantando sua API Smithy
Seção intitulada “Implantando sua API Smithy”O gerador cria infraestrutura CDK ou Terraform baseada no iacProvider
selecionado.
O construct CDK para implantar sua API está na pasta common/constructs
:
import { MyApi } from ':my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // Adicione a API à sua stack const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}
Isso configura:
- Uma função AWS Lambda para o serviço Smithy
- API Gateway REST API como trigger da função
- Roles e permissões IAM
- Grupo de logs CloudWatch
- Configuração de rastreamento X-Ray
Os módulos Terraform para implantar sua API estão na pasta common/terraform
:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Variáveis de ambiente para a função Lambda env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Políticas IAM adicionais se necessário additional_iam_policy_statements = [ # Adicione quaisquer permissões adicionais que sua API precise ]
tags = local.common_tags}
Isso configura:
- Uma função AWS Lambda que serve a API Smithy
- API Gateway REST API como trigger da função
- Roles e permissões IAM
- Grupo de logs CloudWatch
- Configuração de rastreamento X-Ray
- Configuração CORS
O módulo Terraform fornece várias saídas:
# Acesse o endpoint da APIoutput "api_url" { value = module.my_api.stage_invoke_url}
# Acesse detalhes da função Lambdaoutput "lambda_function_name" { value = module.my_api.lambda_function_name}
Integrações
Seção intitulada “Integrações”Os construtos CDK da API REST/HTTP são configurados para fornecer uma interface type-safe para definir integrações para cada uma de suas operações.
Os construtos CDK fornecem suporte completo a integrações type-safe conforme descrito abaixo.
Integrações Padrão
Seção intitulada “Integrações Padrão”Você pode usar o método estático defaultIntegrations
para utilizar o padrão padrão, que define uma função AWS Lambda individual para cada operação:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
Os módulos Terraform automaticamente usam o padrão de roteador com uma única função Lambda. Nenhuma configuração adicional é necessária:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# O módulo cria automaticamente uma única função Lambda # que processa todas as operações da API tags = local.common_tags}
Acessando Integrações
Seção intitulada “Acessando Integrações”Você pode acessar as funções AWS Lambda subjacentes através da propriedade integrations
do construto da API, de forma type-safe. Por exemplo, se sua API define uma operação chamada sayHello
e você precisa adicionar permissões a esta função, você pode fazer isso da seguinte forma:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
// sayHello é tipado conforme as operações definidas em sua APIapi.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));
Com o padrão de roteador do Terraform, há apenas uma função Lambda. Você pode acessá-la através dos outputs do módulo:
# Conceder permissões adicionais à única função 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 Opções Padrão
Seção intitulada “Personalizando Opções Padrão”Se você deseja personalizar as opções usadas ao criar a função Lambda para cada integração padrão, pode usar o método withDefaultOptions
. Por exemplo, se deseja que todas suas funções Lambda residam em uma VPC:
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});
Para personalizar opções como configuração de VPC, você precisa editar o módulo Terraform gerado. Por exemplo, para adicionar suporte a VPC em todas as funções Lambda:
# Adicionar variáveis de VPCvariable "vpc_subnet_ids" { description = "Lista de IDs de subnets VPC para a função Lambda" type = list(string) default = []}
variable "vpc_security_group_ids" { description = "Lista de IDs de grupos de segurança VPC para a função Lambda" type = list(string) default = []}
# Atualizar o recurso da função Lambdaresource "aws_lambda_function" "api_lambda" { # ... configuração existente ...
# Adicionar configuração de VPC vpc_config { subnet_ids = var.vpc_subnet_ids security_group_ids = var.vpc_security_group_ids }}
Então usar o módulo com configuração de VPC:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Configuração de 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}
Sobrescrevendo Integrações
Seção intitulada “Sobrescrevendo Integrações”Você também pode sobrescrever integrações para operações específicas usando o método withOverrides
. Cada sobrescrita deve especificar uma propriedade integration
que é tipada ao construto de integração CDK apropriado para a API HTTP ou REST. O método withOverrides
também é type-safe. Por exemplo, se você deseja sobrescrever uma API getDocumentation
para apontar para documentação hospedada em um site externo:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), }, }) .build(),});
Você notará que a integração sobrescrita não terá mais uma propriedade handler
quando acessada via api.integrations.getDocumentation
.
Você pode adicionar propriedades adicionais a uma integração que também serão tipadas adequadamente, permitindo que outros tipos de integração sejam abstraídos mas permaneçam type-safe. Por exemplo, se você criou uma integração S3 para uma API REST e depois deseja referenciar o bucket para uma operação específica:
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(),});
// Posteriormente, talvez em outro arquivo, você pode acessar a propriedade bucket que definimos// de forma type-safeapi.integrations.getFile.bucket.grantRead(...);
Sobrescrevendo Autorizadores
Seção intitulada “Sobrescrevendo Autorizadores”Você também pode fornecer options
em sua integração para sobrescrever opções específicas de método como autorizadores. Por exemplo, se desejar usar autenticação Cognito para sua operação getDocumentation
:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), options: { authorizer: new CognitoUserPoolsAuthorizer(...) // para REST, ou HttpUserPoolAuthorizer para HTTP API } }, }) .build(),});
Integrações Explícitas
Seção intitulada “Integrações Explícitas”Se preferir, você pode optar por não usar as integrações padrão e fornecer diretamente uma para cada operação. Isso é útil se, por exemplo, cada operação precisar usar um tipo diferente de integração ou se você quiser receber um erro de tipo ao adicionar novas operações:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});
Para integrações explícitas por operação com Terraform, você deve modificar o módulo específico da aplicação gerado para substituir a integração proxy padrão por integrações específicas para cada operação.
Edite packages/common/terraform/src/app/apis/my-api/my-api.tf
:
- Remover as rotas proxy padrão (ex:
resource "aws_apigatewayv2_route" "proxy_routes"
) - Substituir a função Lambda única por funções individuais para cada operação
- Criar integrações e rotas específicas para cada operação, reutilizando o mesmo pacote ZIP:
# Remover função Lambda única padrão 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 # ... restante da configuração }
# Remover integração proxy padrão 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 # ... restante da configuração }
# Remover rotas proxy padrão 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}" # ... restante da configuração }
# Adicionar funções Lambda individuais para cada operação usando o mesmo pacote 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 operação 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 operação 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 }
# Adicionar integrações específicas para cada operação 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" }
# Adicionar rotas específicas para cada operação 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" }
# Adicionar permissões Lambda para cada função 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}/*/*" }
# Remover função Lambda única padrão 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 # ... restante da configuração }
# Remover integração proxy padrão 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 # ... restante da configuração }
# Remover rotas proxy padrão 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}" # ... restante da configuração }
# Adicionar funções Lambda individuais para cada operação usando o mesmo pacote 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 operação 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 operação 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 }
# Adicionar recursos e métodos específicos para cada operação 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" }
# Atualizar deployment para depender das novas integrações~ 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, ])) } }
# Adicionar permissões Lambda para cada função 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}/*/*" }
Padrão de Roteador
Seção intitulada “Padrão de Roteador”Se você preferir implantar uma única função Lambda para atender todas as requisições da API, pode livremente editar o método defaultIntegrations
de sua API para criar uma única função em vez de uma por integração:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { const router = new Function(scope, 'RouterHandler', { ... }); return IntegrationBuilder.rest({ ... defaultIntegrationOptions: {}, buildDefaultIntegration: (op) => { return { // Referencia o mesmo router lambda handler em todas as integrações integration: new LambdaIntegration(router), }; }, }); };}
Você pode modificar o código de outras formas se preferir, por exemplo pode definir a função router
como parâmetro para defaultIntegrations
em vez de construí-la dentro do método.
Os módulos Terraform automaticamente usam o padrão de roteador - esta é a abordagem padrão e única suportada. O módulo gerado cria uma única função Lambda que processa todas as operações da API.
Você pode simplesmente instanciar o módulo padrão para obter o padrão de roteador:
# Padrão de roteador - função Lambda única para todas as operaçõesmodule "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Função Lambda única processa todas as operações automaticamente tags = local.common_tags}
Geração de Código
Seção intitulada “Geração de Código”Como as operações são definidas em Smithy, usamos geração de código para fornecer metadados ao construct CDK para integrações tipadas.
Um target generate:<ApiName>-metadata
é adicionado ao project.json
dos constructs comuns para facilitar esta geração de código, que emite um arquivo como packages/common/constructs/src/generated/my-api/metadata.gen.ts
. Como isso é gerado em tempo de build, é ignorado no controle de versão.
Concedendo Acesso (Apenas IAM)
Seção intitulada “Concedendo Acesso (Apenas IAM)”Se você selecionou autenticação IAM
, pode usar o método grantInvokeAccess
para conceder acesso à sua API:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);
# Crie uma política IAM para permitir invocar a APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Política para permitir invocar a API Smithy"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Anexe a política a uma role 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 sua API Smithy
Seção intitulada “Invocando sua API Smithy”Para invocar sua API de um website React, você pode usar o gerador api-connection
, que fornece geração de cliente tipado a partir do seu modelo Smithy.