FastAPI
FastAPI é um framework para construção de APIs em Python.
O gerador FastAPI cria uma nova aplicação FastAPI com configuração de infraestrutura AWS CDK ou Terraform. O backend gerado utiliza AWS Lambda para implantação serverless, exposto via AWS API Gateway. Configura AWS Lambda Powertools para observabilidade, incluindo registro de logs, rastreamento com AWS X-Ray e métricas no CloudWatch.
Gerar uma API FastAPI
Seção intitulada “Gerar uma API FastAPI”Você pode gerar uma nova API FastAPI 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 - py#api - Preencha os parâmetros obrigatórios
- framework: fastapi
- Clique em
Generate
pnpm nx g @aws/nx-plugin:py#api --framework=fastapiyarn nx g @aws/nx-plugin:py#api --framework=fastapinpx nx g @aws/nx-plugin:py#api --framework=fastapibunx nx g @aws/nx-plugin:py#api --framework=fastapiVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
pnpm nx g @aws/nx-plugin:py#api --framework=fastapi --dry-runyarn nx g @aws/nx-plugin:py#api --framework=fastapi --dry-runnpx nx g @aws/nx-plugin:py#api --framework=fastapi --dry-runbunx nx g @aws/nx-plugin:py#api --framework=fastapi --dry-run| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
| name Obrigatório | string | - | Nome do projeto de API a ser gerado |
| framework | fastapi | fastapi | O framework de API a ser utilizado. |
| integrationPattern | isolated | shared | isolated | Como as integrações do API Gateway são geradas para a API. Escolha entre isolated (padrão) e shared. |
| auth | iam | cognito | custom | iam | O método usado para autenticar com sua API. Escolha entre iam (padrão), cognito ou custom. |
| directory | string | packages | O diretório para armazenar a aplicação. |
| subDirectory | string | - | O subdiretório onde o projeto é colocado. Por padrão, este é o nome do projeto. |
| iac | inherit | cdk | terraform | inherit | O provedor IaC preferido. Por padrão, isso é herdado da sua seleção inicial. |
| moduleName | string | - | Nome do módulo Python |
| infra | rest-lambda | http-lambda | none | rest-lambda | O tipo de infraestrutura a ser usado para implantar esta API. |
| preferInstallDependencies | boolean | true | Se deve preferir instalar dependências após a execução do gerador. Defina como false para adiar a instalação ao executar múltiplos geradores em lote (uma instalação ainda é executada se necessário para que geradores subsequentes possam calcular o grafo de projetos Nx); instale uma vez no final. |
Saída do Gerador
Seção intitulada “Saída do Gerador”O gerador criará a seguinte estrutura de projeto no diretório <directory>/<api-name>:
- project.json Configuração do projeto e alvos de build
- pyproject.toml Configuração do projeto Python e dependências
- run.sh Script de bootstrap do Lambda Web Adapter para iniciar o app FastAPI via uvicorn
Directory<module_name>
- __init__.py Inicialização do módulo
- init.py Configura o app FastAPI e middleware do powertools
- main.py Implementação da API
Directoryscripts
- generate_open_api.py Script para gerar schema OpenAPI a partir do app FastAPI
Infraestrutura
Seção intitulada “Infraestrutura”Como este gerador fornece infraestrutura como código com base 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 está estruturado da seguinte forma:
Directorypackages/common/constructs
Directorysrc
Directoryapp/ Constructs para infraestrutura específica de um projeto/gerador
- …
Directorycore/ Constructs genéricos reutilizados pelos constructs em
app- …
- index.ts Ponto de entrada exportando os constructs de
app
- project.json Metas de build e configuração do projeto
Directorypackages/common/terraform
Directorysrc
Directoryapp/ Módulos Terraform para infraestrutura específica de um projeto/gerador
- …
Directorycore/ Módulos genéricos reutilizados pelos módulos em
app- …
- project.json Metas de build e configuração do projeto
Para implantar sua API, os seguintes arquivos são gerados:
Directorypackages/common/constructs/src
Directoryapp
Directoryapis
- <project-name>.ts Construto CDK para implantar sua API
Directorycore
Directoryapi
- http-api.ts Construto CDK para implantar uma API HTTP (se você escolheu implantar uma API HTTP)
- rest-api.ts Construto CDK para implantar uma API REST (se você escolheu implantar uma API REST)
- utils.ts Utilitários para os construtos da API
Directorypackages/common/terraform/src
Directoryapp
Directoryapis
Directory<project-name>
- <project-name>.tf Módulo para implantar sua API
Directorycore
Directoryapi
Directoryhttp-api
- http-api.tf Módulo para implantar uma API HTTP (se você escolheu implantar uma API HTTP)
Directoryrest-api
- rest-api.tf Módulo para implantar uma API REST (se você escolheu implantar uma API REST)
Arquitetura
Seção intitulada “Arquitetura”A aplicação implantada possui a seguinte arquitetura:
As REST APIs incluem uma Web ACL do AWS WAFv2 na frente do estágio do API Gateway com o conjunto de regras padrão gerenciado pela AWS habilitado.
As HTTP APIs não suportam WAF diretamente — se você precisar de proteção WAF, escolha REST API ou coloque a HTTP API atrás de uma distribuição CloudFront.
Implementando sua API FastAPI
Seção intitulada “Implementando sua API FastAPI”A implementação principal da API está em main.py. É aqui que você define suas rotas e suas implementações. Exemplo:
from pydantic import BaseModelfrom .init import app, tracer
class Item(BaseModel): name: str
@app.get("/items/{item_id}")@tracer.capture_methoddef get_item(item_id: int) -> Item: return Item(name=...)
@app.post("/items")@tracer.capture_methoddef create_item(item: Item): return ...O gerador configura automaticamente vários recursos:
- Integração com AWS Lambda Powertools para observabilidade
- Middleware de tratamento de erros
- Correlação de requisições/respostas
- Coleta de métricas
- Implantação AWS Lambda via Lambda Web Adapter com uvicorn
- Streaming type-safe (somente REST API)
Observabilidade com AWS Lambda Powertools
Seção intitulada “Observabilidade com AWS Lambda Powertools”O gerador configura logging estruturado usando AWS Lambda Powertools. Você pode acessar o logger nos handlers de rota:
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}O logger inclui automaticamente:
- IDs de correlação para rastreamento de requisições
- Caminho e método da requisição
- Informações de contexto do Lambda
- Indicadores de cold start
Rastreamento
Seção intitulada “Rastreamento”O rastreamento com AWS X-Ray é configurado automaticamente. Você pode adicionar subsegmentos personalizados aos seus traces:
from .init import app, tracer
@app.get("/items/{item_id}")@tracer.capture_methoddef read_item(item_id: int): # Cria um novo subsegmento with tracer.provider.in_subsegment("fetch-item-details"): # Sua lógica aqui return {"item_id": item_id}Métricas
Seção intitulada “Métricas”Métricas no CloudWatch são coletadas automaticamente para cada requisição. Você pode adicionar 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}Métricas padrão incluem:
- Contagem de requisições
- Contagem de sucessos/falhas
- Métricas de cold start
- Métricas por rota
Tratamento de Erros
Seção intitulada “Tratamento de Erros”O gerador inclui tratamento abrangente de erros:
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}Exceções não tratadas são capturadas pelo middleware e:
- Registram a exceção completa com stack trace
- Gravam métrica de falha
- Retornam resposta 500 segura ao cliente
- Preservam o ID de correlação
Acessando o Usuário Chamador
Seção intitulada “Acessando o Usuário Chamador”Quando sua API é protegida por autenticação, seus manipuladores de rota frequentemente precisam saber quem está chamando. A FastAPI gerada é executada dentro do AWS Lambda via o Lambda Web Adapter, que encaminha o contexto da requisição do API Gateway como JSON no cabeçalho x-amzn-request-context. Você pode lê-lo do Request do FastAPI para extrair a identidade do chamador.
Como exemplo, vamos adicionar um endpoint /me que retorna detalhes sobre o usuário chamador. Implementaremos a extração como uma dependência do FastAPI para que possa ser reutilizada entre rotas. A forma do contexto da requisição — e portanto como você extrai a identidade — depende tanto do método auth selecionado quanto se você implantou uma REST API ou HTTP API.
Para autenticação IAM, procuramos o chamador no Cognito usando o sub extraído do contexto da requisição do API Gateway. Crie identity.py ao lado de main.py:
import jsonimport osfrom typing import Annotated
from boto3 import clientfrom fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
cognito = client("cognito-idp")
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # O Lambda Web Adapter encaminha o contexto da requisição do API Gateway como JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) provider = request_context.get("identity", {}).get("cognitoAuthenticationProvider")
sub = provider.split(":")[-1] if provider else None if not sub: raise HTTPException(status_code=403, detail="Unable to determine calling user")
users = cognito.list_users( # Assume que o id do user pool está configurado no ambiente do lambda UserPoolId=os.environ["USER_POOL_ID"], Limit=1, Filter=f'sub="{sub}"', ).get("Users", [])
if len(users) != 1: raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
return Identity(sub=sub, username=users[0]["Username"])
CurrentUser = Annotated[Identity, Depends(get_identity)]import jsonimport osfrom typing import Annotated
from boto3 import clientfrom fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
cognito = client("cognito-idp")
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # O Lambda Web Adapter encaminha o contexto da requisição do API Gateway como JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) amr = ( request_context.get("authorizer", {}) .get("iam", {}) .get("cognitoIdentity", {}) .get("amr", []) ) sign_in = next((s for s in amr if ":CognitoSignIn:" in s), None) sub = sign_in.split(":")[-1] if sign_in else None
if not sub: raise HTTPException(status_code=403, detail="Unable to determine calling user")
users = cognito.list_users( # Assume que o id do user pool está configurado no ambiente do lambda UserPoolId=os.environ["USER_POOL_ID"], Limit=1, Filter=f'sub="{sub}"', ).get("Users", [])
if len(users) != 1: raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
return Identity(sub=sub, username=users[0]["Username"])
CurrentUser = Annotated[Identity, Depends(get_identity)]Com auth: 'cognito', o autorizador Cognito User Pools do API Gateway verifica o JWT que o chamador fornece no cabeçalho Authorization e coloca as claims verificadas no contexto da requisição.
Crie identity.py ao lado de main.py:
import jsonfrom typing import Annotated
from fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # O Lambda Web Adapter encaminha o contexto da requisição do API Gateway como JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) claims = request_context.get("authorizer", {}).get("claims", {})
sub = claims.get("sub") username = claims.get("username")
if not sub or not username: raise HTTPException(status_code=403, detail="Unable to determine calling user")
return Identity(sub=sub, username=username)
CurrentUser = Annotated[Identity, Depends(get_identity)]HTTP APIs usam um autorizador JWT que coloca as claims verificadas em authorizer.jwt.claims:
import jsonfrom typing import Annotated
from fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # O Lambda Web Adapter encaminha o contexto da requisição do API Gateway como JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) claims = request_context.get("authorizer", {}).get("jwt", {}).get("claims", {})
sub = claims.get("sub") username = claims.get("username")
if not sub or not username: raise HTTPException(status_code=403, detail="Unable to determine calling user")
return Identity(sub=sub, username=username)
CurrentUser = Annotated[Identity, Depends(get_identity)]Você pode então injetar a dependência CurrentUser em qualquer rota que precise da identidade do chamador:
from .identity import CurrentUser, Identityfrom .init import app, tracer
@app.get("/me")@tracer.capture_methoddef me(identity: CurrentUser) -> Identity: return identityStreaming
Seção intitulada “Streaming”A FastAPI gerada suporta respostas em streaming nativamente ao usar REST API. A infraestrutura é configurada para usar o AWS Lambda Web Adapter para executar sua FastAPI via uvicorn dentro do Lambda, com ResponseTransferMode.STREAM no API Gateway para todas as operações REST API, o que permite que streaming funcione junto com operações não-streaming.
Usando JsonStreamingResponse
Seção intitulada “Usando JsonStreamingResponse”O arquivo init.py gerado exporta uma classe JsonStreamingResponse que fornece streaming type-safe com geração adequada de schema OpenAPI. Isso garante que o gerador connection possa produzir métodos cliente de streaming corretamente tipados.
from pydantic import BaseModelfrom .init import app, JsonStreamingResponse
class Chunk(BaseModel): message: str
async def generate_chunks(): for i in range(100): yield Chunk(message=f"This is chunk {i}")
@app.post( "/stream", response_class=JsonStreamingResponse, responses={200: JsonStreamingResponse.openapi_response(Chunk, "Stream of chunks")},)async def my_stream() -> JsonStreamingResponse: return JsonStreamingResponse(generate_chunks())A classe JsonStreamingResponse:
- Serializa modelos Pydantic para o formato JSON Lines (
application/jsonl) - Fornece um helper
openapi_responseque gera o schema OpenAPI correto comitemSchema, permitindo que o geradorconnectionproduza métodos cliente de streaming type-safe
Consumo
Seção intitulada “Consumo”Para consumir streams de respostas, utilize o gerador connection, que fornece um método type-safe para iterar sobre os chunks transmitidos.
Implantando sua API FastAPI
Seção intitulada “Implantando sua API FastAPI”O gerador FastAPI cria código de infraestrutura CDK ou Terraform baseado no iacProvider selecionado. Use para implantar sua FastAPI.
O construct CDK para implantação da API está na pasta common/constructs. Use em uma aplicação CDK:
import { MyApi } from ':my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // Adicione a API ao seu stack const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}Isso configura:
- Uma função AWS Lambda para cada operação na aplicação FastAPI
- API Gateway HTTP/REST como trigger da função
- Roles e permissões IAM
- Log group no CloudWatch
- Configuração de rastreamento X-Ray
- Namespace de métricas no CloudWatch
Os módulos Terraform para implantação da API estão na pasta common/terraform. Use em uma configuração Terraform.
O módulo da API armazena seu zip de implantação Lambda em um bucket S3 de assets compartilhado — veja o guia de infraestrutura Terraform para detalhes. Instancie o módulo core/asset-bucket uma vez por implantação e passe sua saída bucket_name para cada módulo de API / Lambda via a entrada asset_bucket_name:
module "asset_bucket" { source = "../../common/terraform/src/core/asset-bucket"}
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# 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 permissões adicionais que sua API precisa ]
tags = local.common_tags}Isso configura:
- Uma função AWS Lambda que serve todas as rotas FastAPI
- API Gateway HTTP/REST como trigger da função
- Roles e permissões IAM
- Log group no CloudWatch
- Configuração de rastreamento X-Ray
- Configuração de CORS
O módulo Terraform fornece várias saídas que você pode usar:
# Acessar o endpoint da APIoutput "api_url" { value = module.my_api.stage_invoke_url}
# Acessar detalhes da função Lambdaoutput "lambda_function_name" { value = module.my_api.lambda_function_name}
# Acessar role IAM para conceder permissões adicionaisoutput "lambda_execution_role_arn" { value = module.my_api.lambda_execution_role_arn}Você pode personalizar as configurações de CORS passando variáveis ao módulo:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Configuração 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}Para APIs REST, o construto gerado associa um Web ACL do AWS WAFv2 ao estágio do API Gateway por padrão. O Web ACL usa o conjunto de regras padrão gerenciado pela AWS (AWSManagedRulesCommonRuleSet e AWSManagedRulesKnownBadInputsRuleSet), fornecendo proteção contra explorações web comuns, incluindo o OWASP Top 10. Os logs de requisições do WAF são gravados em um grupo do CloudWatch Logs.
Você pode editar o construto rest-api gerado para adicionar, remover ou ajustar regras (por exemplo, para adicionar regras baseadas em taxa ou grupos de regras gerenciadas adicionais).
Para desativar (por exemplo, para anexar seu próprio Web ACL), defina enableWaf como false:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), enableWaf: false,});Para desativar (por exemplo, para anexar seu próprio Web ACL), defina enable_waf como false:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name enable_waf = false}Access logging
Seção intitulada “Access logging”Para APIs REST, a infraestrutura gerada habilita o registro de acesso por padrão, escrevendo uma linha JSON estruturada por solicitação em um grupo dedicado do CloudWatch Logs. O grupo de logs é criptografado com uma chave KMS gerenciada pelo cliente e retido por um ano.
O API Gateway escreve logs de acesso usando uma função do CloudWatch Logs no nível da conta. Essa função é configurada na configuração AWS::ApiGateway::Account, que é um singleton por região por conta — há apenas uma função para cada API REST na região. Para gerenciar isso com segurança em várias pilhas implantadas independentemente, a infraestrutura gerada:
- Cria uma função compartilhada do CloudWatch Logs e a configura na conta somente quando nenhuma função funcional já está definida, para que as implantações nunca substituam uma função que outra pilha possui.
- Deixa a configuração da conta intocada na desmontagem, para que a destruição de uma pilha nunca desabilite o registro para outras APIs REST na região.
A função da conta é gerenciada pelo construto ApiGatewayAccount, um singleton com escopo de pilha resolvido via ApiGatewayAccount.ensure(scope). O estágio de cada API REST depende dele, e a função é configurada por um recurso personalizado baseado em Lambda.
Você pode personalizar o formato do log de acesso passando deployOptions ao construir sua API:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), deployOptions: { accessLogFormat: AccessLogFormat.clf(), },});A função da conta é gerenciada pelo módulo core/api/api-gateway-account, que é instanciado pelo módulo de API gerado. Ele configura a conta de forma idempotente e nunca é redefinido no terraform destroy.
Você pode personalizar o formato do log de acesso editando o bloco access_log_settings no recurso aws_api_gateway_stage no módulo de API gerado.
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"
asset_bucket_name = module.asset_bucket.bucket_name
# 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: [...],}));Se sua API usa o padrão shared, o roteador Lambda compartilhado é exposto como api.integrations.$router:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');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"
asset_bucket_name = module.asset_bucket.bucket_name
# 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}Personalizando Opções por Operação
Seção intitulada “Personalizando Opções por Operação”Para personalizar as opções usadas para criar a integração padrão para operações específicas (sem afetar as outras), você pode usar o método withOperationOptions. Por exemplo, se você deseja aumentar o timeout da função Lambda para apenas uma operação:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOperationOptions({ sayHello: { timeout: Duration.seconds(60), }, }) .build(),});
// As operações selecionadas permanecem integrações padrão, então ainda são tipadas adequadamente:api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ ... }));As opções que você especifica são mescladas com as opções de integração padrão (e quaisquer opções definidas via withDefaultOptions). Note que você não pode especificar opções para operações que você substituiu via withOverrides, pois estas não usam mais a integração padrão.
Você encontrará um erro de tipo se a mesma operação for alvo tanto de withOperationOptions quanto de withOverrides, independentemente da ordem em que você os chamar.
Para personalizar opções para operações específicas com Terraform, você precisa editar o módulo Terraform gerado para configurar funções Lambda individuais por operação (veja a seção Integrações Explícitas abaixo).
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 = 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 = 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 = 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 = 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 Integração
Seção intitulada “Padrão de Integração”Os construtos CDK de API gerados suportam dois padrões de integração:
isolatedcria uma função Lambda por operação. Este é o padrão para APIs geradas.sharedcria um único roteador Lambda padrão e o reutiliza para cada operação, a menos que você sobrescreva integrações específicas.
isolated oferece permissões e configuração mais granulares por operação. shared reduz a proliferação de funções Lambda e integrações do API Gateway, ainda permitindo sobrescritas seletivas.
Por exemplo, definir pattern como 'shared' cria uma única função em vez de uma por integração:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { ... return IntegrationBuilder.rest({ pattern: 'shared', ... }); };}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 padrão - função Lambda única para todas as operaçõesmodule "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# 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 em FastAPI são definidas em Python e a infraestrutura CDK em TypeScript, instrumentamos geração de código para fornecer metadados ao construct CDK e prover uma interface type-safe para integrações.
Um alvo generate:<ApiName>-metadata é adicionado ao project.json dos constructs comuns para facilitar essa 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 (Somente IAM)
Seção intitulada “Concedendo Acesso (Somente IAM)”Se você selecionou autenticação IAM, pode usar o método grantInvokeAccess para conceder acesso à sua API:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);# Criar uma política IAM para permitir invocar a APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Política para permitir invocar a FastAPI"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Anexar a política a uma role IAM (ex: para usuários autenticados)resource "aws_iam_role_policy_attachment" "api_invoke_access" { role = aws_iam_role.authenticated_user_role.name policy_arn = aws_iam_policy.api_invoke_policy.arn}
# Ou anexar a uma role existente pelo nomeresource "aws_iam_role_policy_attachment" "api_invoke_access_existing" { role = "MyExistingRole" policy_arn = aws_iam_policy.api_invoke_policy.arn}As principais saídas do módulo da API que você pode usar para políticas IAM são:
module.my_api.api_execution_arn- Para conceder permissões execute-api:Invokemodule.my_api.api_arn- O ARN do API Gatewaymodule.my_api.lambda_function_arn- O ARN da função Lambda
Desenvolvimento Local
Seção intitulada “Desenvolvimento Local”O gerador configura um servidor de desenvolvimento local que você pode executar com:
pnpm nx serve my-apiyarn nx serve my-apinpx nx serve my-apibunx nx serve my-apiIsso inicia um servidor de desenvolvimento FastAPI local com:
- Recarregamento automático em alterações de código
- Documentação interativa da API em
/docsou/redoc - Schema OpenAPI em
/openapi.json
Invocando sua API FastAPI
Seção intitulada “Invocando sua API FastAPI”Para invocar sua API de um site React, você pode usar o gerador connection.
Conexões
Seção intitulada “Conexões”Use o gerador connection para integrar este projeto com outros em seu workspace. As seguintes conexões envolvem este projeto: