FastAPI
FastAPI è un framework per la creazione di API in Python.
Il generatore FastAPI crea una nuova FastAPI con configurazione dell’infrastruttura AWS CDK o Terraform. Il backend generato utilizza AWS Lambda per il deployment serverless, esposto tramite un’API AWS API Gateway. Configura AWS Lambda Powertools per l’osservabilità, inclusi logging, tracing AWS X-Ray e metriche Cloudwatch.
Utilizzo
Sezione intitolata “Utilizzo”Generare una FastAPI
Sezione intitolata “Generare una FastAPI”Puoi generare una nuova FastAPI in due modi:
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=fastapiPuoi anche eseguire una prova per vedere quali file verrebbero modificati
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- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - py#api - Compila i parametri richiesti
- framework: fastapi
- Clicca su
Generate
Opzioni
Sezione intitolata “Opzioni”| Parametro | Tipo | Predefinito | Descrizione |
|---|---|---|---|
| name Obbligatorio | string | - | Nome del progetto API da generare |
| framework | fastapi | fastapi | Il framework API da utilizzare. |
| integrationPattern | isolated | shared | isolated | Come vengono generate le integrazioni API Gateway per l'API. Scegli tra isolated (predefinito) e shared. |
| auth | iam | cognito | custom | iam | Il metodo utilizzato per autenticare con la tua API. Scegli tra iam (predefinito), cognito o custom. |
| directory | string | packages | La directory in cui memorizzare l'applicazione. |
| subDirectory | string | - | La sottodirectory in cui viene posizionato il progetto. Per impostazione predefinita corrisponde al nome del progetto. |
| iac | inherit | cdk | terraform | inherit | Il provider IaC preferito. Per impostazione predefinita viene ereditato dalla selezione iniziale. |
| moduleName | string | - | Nome del modulo Python |
| infra | rest-lambda | http-lambda | none | rest-lambda | Il tipo di infrastruttura da utilizzare per distribuire questa API. |
| preferInstallDependencies | boolean | true | Se preferire l'installazione delle dipendenze dopo l'esecuzione del generatore. Impostare su false per rimandare l'installazione quando si eseguono più generatori in batch (l'installazione viene comunque eseguita se necessaria affinché i generatori successivi possano calcolare il grafo dei progetti Nx); installare una volta alla fine. |
Output del generatore
Sezione intitolata “Output del generatore”Il generatore creerà la seguente struttura di progetto nella directory <directory>/<api-name>:
- project.json Configurazione del progetto e target di build
- pyproject.toml Configurazione del progetto Python e dipendenze
- run.sh Script di bootstrap Lambda Web Adapter per avviare l’app FastAPI tramite uvicorn
Directory<module_name>
- __init__.py Inizializzazione del modulo
- init.py Configura l’app FastAPI e configura il middleware powertools
- main.py Implementazione dell’API
Directoryscripts
- generate_open_api.py Script per generare uno schema OpenAPI dall’app FastAPI
Infrastruttura
Sezione intitolata “Infrastruttura”Poiché questo generatore fornisce infrastruttura come codice basata sul tuo iac scelto, creerà un progetto in packages/common che include i costrutti CDK o i moduli Terraform pertinenti.
Il progetto comune di infrastruttura come codice è strutturato come segue:
Directorypackages/common/constructs
Directorysrc
Directoryapp/ Constructs for infrastructure specific to a project/generator
- …
Directorycore/ Generic constructs which are reused by constructs in
app- …
- index.ts Entry point exporting constructs from
app
- project.json Project build targets and configuration
Directorypackages/common/terraform
Directorysrc
Directoryapp/ Terraform modules for infrastructure specific to a project/generator
- …
Directorycore/ Generic modules which are reused by modules in
app- …
- project.json Project build targets and configuration
Per il deployment della tua API, vengono generati i seguenti file:
Directorypackages/common/constructs/src
Directoryapp
Directoryapis
- <project-name>.ts CDK construct for deploying your API
Directorycore
Directoryapi
- http-api.ts CDK construct for deploying an HTTP API (if you selected to deploy an HTTP API)
- rest-api.ts CDK construct for deploying a REST API (if you selected to deploy a REST API)
- utils.ts Utilities for the API constructs
Directorypackages/common/terraform/src
Directoryapp
Directoryapis
Directory<project-name>
- <project-name>.tf Module for deploying your API
Directorycore
Directoryapi
Directoryhttp-api
- http-api.tf Module for deploying an HTTP API (if you selected to deploy an HTTP API)
Directoryrest-api
- rest-api.tf Module for deploying a REST API (if you selected to deploy a REST API)
Architettura
Sezione intitolata “Architettura”L’applicazione distribuita ha la seguente architettura:
Le REST API includono una Web ACL AWS WAFv2 davanti allo stage di API Gateway con il set di regole predefinito gestito da AWS abilitato.
Le HTTP API non supportano direttamente WAF — se hai bisogno della protezione WAF, scegli REST API oppure posiziona l’HTTP API dietro una distribuzione CloudFront.
Implementare la tua FastAPI
Sezione intitolata “Implementare la tua FastAPI”L’implementazione principale dell’API si trova in main.py. Qui è dove definisci le tue route API e le loro implementazioni. Ecco un esempio:
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 ...Il generatore configura automaticamente diverse funzionalità:
- Integrazione di AWS Lambda Powertools per l’osservabilità
- Middleware per la gestione degli errori
- Correlazione richiesta/risposta
- Raccolta di metriche
- Deployment AWS Lambda tramite Lambda Web Adapter con uvicorn
- Streaming type-safe (solo REST API)
Osservabilità con AWS Lambda Powertools
Sezione intitolata “Osservabilità con AWS Lambda Powertools”Logging
Sezione intitolata “Logging”Il generatore configura il logging strutturato utilizzando AWS Lambda Powertools. Puoi accedere al logger nei tuoi handler di route:
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}Il logger include automaticamente:
- ID di correlazione per il tracciamento delle richieste
- Percorso e metodo della richiesta
- Informazioni sul contesto Lambda
- Indicatori di cold start
Tracing
Sezione intitolata “Tracing”Il tracing AWS X-Ray è configurato automaticamente. Puoi aggiungere subsegmenti personalizzati alle tue tracce:
from .init import app, tracer
@app.get("/items/{item_id}")@tracer.capture_methoddef read_item(item_id: int): # Creates a new subsegment with tracer.provider.in_subsegment("fetch-item-details"): # Your logic here return {"item_id": item_id}Metriche
Sezione intitolata “Metriche”Le metriche CloudWatch vengono raccolte automaticamente per ogni richiesta. Puoi aggiungere metriche personalizzate:
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}Le metriche predefinite includono:
- Conteggi delle richieste
- Conteggi di successo/fallimento
- Metriche di cold start
- Metriche per route
Gestione degli errori
Sezione intitolata “Gestione degli errori”Il generatore include una gestione completa degli errori:
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}Le eccezioni non gestite vengono catturate dal middleware e:
- Registrano l’eccezione completa con stack trace
- Registrano una metrica di fallimento
- Restituiscono una risposta 500 sicura al client
- Preservano l’ID di correlazione
Accedere all’utente chiamante
Sezione intitolata “Accedere all’utente chiamante”Quando la tua API è protetta da autenticazione, i tuoi handler di route spesso devono sapere chi sta chiamando. La FastAPI generata viene eseguita all’interno di AWS Lambda tramite il Lambda Web Adapter, che inoltra il contesto della richiesta API Gateway come JSON nell’header x-amzn-request-context. Puoi leggerlo dalla Request FastAPI per estrarre l’identità del chiamante.
Come esempio, aggiungiamo un endpoint /me che restituisce i dettagli sull’utente chiamante. Implementeremo l’estrazione come una dipendenza FastAPI in modo che possa essere riutilizzata tra le route. La forma del contesto della richiesta — e quindi come estrarre l’identità — dipende sia dal metodo auth selezionato sia dal fatto che tu abbia distribuito una REST o HTTP API.
Per l’autenticazione IAM, cerchiamo il chiamante in Cognito utilizzando il sub estratto dal contesto della richiesta API Gateway. Crea identity.py accanto a main.py:
import jsonimport osfrom typing import Annotated
from boto3 import clientfrom fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
cognito = client("cognito-idp")
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # The Lambda Web Adapter forwards the API Gateway request context as JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) provider = request_context.get("identity", {}).get("cognitoAuthenticationProvider")
sub = provider.split(":")[-1] if provider else None if not sub: raise HTTPException(status_code=403, detail="Unable to determine calling user")
users = cognito.list_users( # Assumes user pool id is configured in lambda environment UserPoolId=os.environ["USER_POOL_ID"], Limit=1, Filter=f'sub="{sub}"', ).get("Users", [])
if len(users) != 1: raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
return Identity(sub=sub, username=users[0]["Username"])
CurrentUser = Annotated[Identity, Depends(get_identity)]import jsonimport osfrom typing import Annotated
from boto3 import clientfrom fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
cognito = client("cognito-idp")
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # The Lambda Web Adapter forwards the API Gateway request context as JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) amr = ( request_context.get("authorizer", {}) .get("iam", {}) .get("cognitoIdentity", {}) .get("amr", []) ) sign_in = next((s for s in amr if ":CognitoSignIn:" in s), None) sub = sign_in.split(":")[-1] if sign_in else None
if not sub: raise HTTPException(status_code=403, detail="Unable to determine calling user")
users = cognito.list_users( # Assumes user pool id is configured in lambda environment UserPoolId=os.environ["USER_POOL_ID"], Limit=1, Filter=f'sub="{sub}"', ).get("Users", [])
if len(users) != 1: raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
return Identity(sub=sub, username=users[0]["Username"])
CurrentUser = Annotated[Identity, Depends(get_identity)]Con auth: 'cognito', l’autorizzatore Cognito User Pools di API Gateway verifica il JWT che il chiamante fornisce nell’header Authorization e posiziona i claim verificati sul contesto della richiesta.
Crea identity.py accanto a main.py:
import jsonfrom typing import Annotated
from fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # The Lambda Web Adapter forwards the API Gateway request context as JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) claims = request_context.get("authorizer", {}).get("claims", {})
sub = claims.get("sub") username = claims.get("username")
if not sub or not username: raise HTTPException(status_code=403, detail="Unable to determine calling user")
return Identity(sub=sub, username=username)
CurrentUser = Annotated[Identity, Depends(get_identity)]Le HTTP API utilizzano un autorizzatore JWT che posiziona i claim verificati sotto authorizer.jwt.claims:
import jsonfrom typing import Annotated
from fastapi import Depends, HTTPException, Requestfrom pydantic import BaseModel
class Identity(BaseModel): sub: str username: str
def get_identity(request: Request) -> Identity: # The Lambda Web Adapter forwards the API Gateway request context as JSON request_context_header = request.headers.get("x-amzn-request-context") if not request_context_header: raise HTTPException(status_code=403, detail="Unable to determine calling user")
request_context = json.loads(request_context_header) claims = request_context.get("authorizer", {}).get("jwt", {}).get("claims", {})
sub = claims.get("sub") username = claims.get("username")
if not sub or not username: raise HTTPException(status_code=403, detail="Unable to determine calling user")
return Identity(sub=sub, username=username)
CurrentUser = Annotated[Identity, Depends(get_identity)]Puoi quindi iniettare la dipendenza CurrentUser in qualsiasi route che necessita dell’identità del chiamante:
from .identity import CurrentUser, Identityfrom .init import app, tracer
@app.get("/me")@tracer.capture_methoddef me(identity: CurrentUser) -> Identity: return identityStreaming
Sezione intitolata “Streaming”La FastAPI generata supporta le risposte in streaming out of the box quando si utilizza una REST API. L’infrastruttura è configurata per utilizzare il AWS Lambda Web Adapter per eseguire la tua FastAPI tramite uvicorn all’interno di Lambda, con ResponseTransferMode.STREAM in API Gateway per tutte le operazioni REST API, che consente allo streaming di funzionare insieme alle operazioni non in streaming.
Utilizzo di JsonStreamingResponse
Sezione intitolata “Utilizzo di JsonStreamingResponse”Il file init.py generato esporta una classe JsonStreamingResponse che fornisce streaming type-safe con generazione corretta dello schema OpenAPI. Questo garantisce che il generatore connection possa produrre metodi client di streaming correttamente tipizzati.
from pydantic import BaseModelfrom .init import app, JsonStreamingResponse
class Chunk(BaseModel): message: str
async def generate_chunks(): for i in range(100): yield Chunk(message=f"This is chunk {i}")
@app.post( "/stream", response_class=JsonStreamingResponse, responses={200: JsonStreamingResponse.openapi_response(Chunk, "Stream of chunks")},)async def my_stream() -> JsonStreamingResponse: return JsonStreamingResponse(generate_chunks())La classe JsonStreamingResponse:
- Serializza i modelli Pydantic nel formato JSON Lines (
application/jsonl) - Fornisce un helper
openapi_responseche genera lo schema OpenAPI corretto conitemSchema, consentendo al generatoreconnectiondi produrre metodi client di streaming type-safe
Consumo
Sezione intitolata “Consumo”Per consumare uno stream di risposte, puoi utilizzare il generatore connection che fornirà un metodo type-safe per iterare sui tuoi chunk in streaming.
Distribuire la tua FastAPI
Sezione intitolata “Distribuire la tua FastAPI”Il generatore FastAPI crea infrastruttura come codice CDK o Terraform in base al tuo iac selezionato. Puoi usarlo per distribuire la tua FastAPI.
Il costrutto CDK per distribuire la tua API nella cartella common/constructs. Puoi usarlo in un’applicazione CDK:
import { MyApi } from '@my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // Add the api to your stack const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}Questo configura:
- Una funzione AWS Lambda per ogni operazione nell’applicazione FastAPI
- API Gateway HTTP/REST API come trigger della funzione
- Ruoli IAM e permessi
- Gruppo di log CloudWatch
- Configurazione del tracing X-Ray
- Namespace delle metriche CloudWatch
I moduli Terraform per distribuire la tua API si trovano nella cartella common/terraform. Puoi usarli in una configurazione Terraform.
Il modulo API prepara il suo zip di deployment Lambda in un bucket S3 di asset condiviso — vedi la guida all’infrastruttura Terraform per i dettagli. Istanzia il modulo core/asset-bucket una volta per deployment e passa il suo output bucket_name in ogni modulo API / Lambda tramite l’input asset_bucket_name:
module "asset_bucket" { source = "../../common/terraform/src/core/asset-bucket"}
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Environment variables for the Lambda function env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Additional IAM policies if needed additional_iam_policy_statements = [ # Add any additional permissions your API needs ]
tags = local.common_tags}Questo configura:
- Una funzione AWS Lambda che serve tutte le route FastAPI
- API Gateway HTTP/REST API come trigger della funzione
- Ruoli IAM e permessi
- Gruppo di log CloudWatch
- Configurazione del tracing X-Ray
- Configurazione CORS
Il modulo Terraform fornisce diversi output che puoi utilizzare:
# Access the API endpointoutput "api_url" { value = module.my_api.stage_invoke_url}
# Access Lambda function detailsoutput "lambda_function_name" { value = module.my_api.lambda_function_name}
# Access IAM role for granting additional permissionsoutput "lambda_execution_role_arn" { value = module.my_api.lambda_execution_role_arn}Puoi personalizzare le impostazioni CORS passando variabili al modulo:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Custom CORS configuration cors_allow_origins = ["https://myapp.com", "https://staging.myapp.com"] cors_allow_methods = ["GET", "POST", "PUT", "DELETE"] cors_allow_headers = [ "authorization", "content-type", "x-custom-header" ]
tags = local.common_tags}Per le REST API, il costrutto generato associa un Web ACL AWS WAFv2 allo stage di API Gateway per impostazione predefinita. Il Web ACL utilizza il set di regole predefinito gestito da AWS (AWSManagedRulesCommonRuleSet e AWSManagedRulesKnownBadInputsRuleSet), fornendo protezione contro exploit web comuni inclusa la OWASP Top 10. I log delle richieste WAF vengono scritti in un gruppo di CloudWatch Logs.
Puoi modificare il costrutto rest-api generato per aggiungere, rimuovere o regolare le regole (ad esempio, per aggiungere regole basate sulla frequenza o gruppi di regole gestite aggiuntivi).
Per disattivare (ad esempio, per collegare il tuo Web ACL), imposta enableWaf su false:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), enableWaf: false,});Per disattivare (ad esempio, per collegare il tuo Web ACL), imposta enable_waf su false:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name enable_waf = false}Logging degli accessi
Sezione intitolata “Logging degli accessi”Per le REST API, l’infrastruttura generata abilita la registrazione degli accessi per impostazione predefinita, scrivendo una riga JSON strutturata per richiesta in un gruppo CloudWatch Logs dedicato. Il gruppo di log è crittografato con una chiave KMS gestita dal cliente e conservato per un anno.
API Gateway scrive i log di accesso utilizzando un ruolo CloudWatch Logs a livello di account. Questo ruolo è configurato sull’impostazione AWS::ApiGateway::Account, che è un singleton per regione per account — esiste un solo ruolo per ogni REST API nella regione. Per gestire questo in modo sicuro tra più stack distribuiti indipendentemente, l’infrastruttura generata:
- Crea un ruolo CloudWatch Logs condiviso e lo configura sull’account solo quando non è già impostato un ruolo funzionante, in modo che le distribuzioni non sovrascrivano mai un ruolo di proprietà di un altro stack.
- Lascia intatta l’impostazione dell’account durante lo smantellamento, in modo che la distruzione di uno stack non disabiliti mai la registrazione per altre REST API nella regione.
Il ruolo dell’account è gestito dal costrutto ApiGatewayAccount, un singleton con ambito stack risolto tramite ApiGatewayAccount.ensure(scope). Ogni stage della REST API dipende da esso e il ruolo è configurato da una risorsa personalizzata supportata da Lambda.
Puoi personalizzare il formato del log di accesso passando deployOptions durante la costruzione della tua API:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), deployOptions: { accessLogFormat: AccessLogFormat.clf(), },});Il ruolo dell’account è gestito dal modulo core/api/api-gateway-account, che viene istanziato dal modulo API generato. Configura l’account in modo idempotente e non viene mai reimpostato con terraform destroy.
Puoi personalizzare il formato del log di accesso modificando il blocco access_log_settings sulla risorsa aws_api_gateway_stage nel modulo API generato.
Integrazioni
Sezione intitolata “Integrazioni”I costrutti CDK REST/HTTP API sono configurati per fornire un’interfaccia type-safe per definire integrazioni per ciascuna delle tue operazioni.
I costrutti CDK forniscono supporto completo per integrazioni type-safe come descritto di seguito.
Integrazioni Predefinite
Sezione intitolata “Integrazioni Predefinite”Puoi utilizzare il metodo statico defaultIntegrations per utilizzare il pattern predefinito, che definisce una funzione AWS Lambda individuale per ogni operazione:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});I moduli Terraform utilizzano automaticamente il pattern router con una singola funzione Lambda. Non è necessaria alcuna configurazione aggiuntiva:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# The module automatically creates a single Lambda function # that handles all API operations tags = local.common_tags}Accesso alle Integrazioni
Sezione intitolata “Accesso alle Integrazioni”Puoi accedere alle funzioni AWS Lambda sottostanti tramite la proprietà integrations del costrutto API, in modo type-safe. Ad esempio, se la tua API definisce un’operazione chiamata sayHello e devi aggiungere alcune autorizzazioni a questa funzione, puoi farlo come segue:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
// sayHello is typed to the operations defined in your APIapi.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));Se la tua API utilizza il pattern shared, il router Lambda condiviso è esposto come api.integrations.$router:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');Con il pattern router di Terraform, c’è solo una funzione Lambda. Puoi accedervi tramite gli output del modulo:
# Grant additional permissions to the single Lambda functionresource "aws_iam_role_policy" "additional_permissions" { name = "additional-api-permissions" role = module.my_api.lambda_execution_role_name
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject" ] Resource = "arn:aws:s3:::my-bucket/*" } ] })}Personalizzazione delle Opzioni Predefinite
Sezione intitolata “Personalizzazione delle Opzioni Predefinite”Se desideri personalizzare le opzioni utilizzate durante la creazione della funzione Lambda per ogni integrazione predefinita, puoi utilizzare il metodo withDefaultOptions. Ad esempio, se desideri che tutte le tue funzioni Lambda risiedano in un Vpc:
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});Per personalizzare opzioni come la configurazione VPC, devi modificare il modulo Terraform generato. Ad esempio, per aggiungere il supporto VPC a tutte le funzioni Lambda:
# Add VPC variablesvariable "vpc_subnet_ids" { description = "List of VPC subnet IDs for Lambda function" type = list(string) default = []}
variable "vpc_security_group_ids" { description = "List of VPC security group IDs for Lambda function" type = list(string) default = []}
# Update the Lambda function resourceresource "aws_lambda_function" "api_lambda" { # ... existing configuration ...
# Add VPC configuration vpc_config { subnet_ids = var.vpc_subnet_ids security_group_ids = var.vpc_security_group_ids }}Quindi utilizza il modulo con la configurazione VPC:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# VPC configuration 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}Personalizzazione delle Opzioni per Operazione
Sezione intitolata “Personalizzazione delle Opzioni per Operazione”Per personalizzare le opzioni utilizzate per creare l’integrazione predefinita per operazioni specifiche (senza influenzare le altre), puoi utilizzare il metodo withOperationOptions. Ad esempio, se desideri aumentare il timeout della funzione Lambda solo per un’operazione:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOperationOptions({ sayHello: { timeout: Duration.seconds(60), }, }) .build(),});
// The selected operations remain default integrations, so they're still typed accordingly:api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ ... }));Le opzioni che specifichi vengono unite alle opzioni di integrazione predefinite (e a qualsiasi opzione impostata tramite withDefaultOptions). Nota che non puoi specificare opzioni per operazioni che hai sostituito tramite withOverrides, poiché queste non utilizzano più l’integrazione predefinita.
Incontrerai un errore di tipo se la stessa operazione è targetizzata sia da withOperationOptions che da withOverrides, indipendentemente dall’ordine in cui li chiami.
Per personalizzare le opzioni per operazioni specifiche con Terraform, devi modificare il modulo Terraform generato per configurare funzioni Lambda individuali per operazione (vedi la sezione Integrazioni Esplicite di seguito).
Override delle Integrazioni
Sezione intitolata “Override delle Integrazioni”Puoi anche sovrascrivere le integrazioni per operazioni specifiche utilizzando il metodo withOverrides. Ogni override deve specificare una proprietà integration che è tipizzata al costrutto di integrazione CDK appropriato per l’API HTTP o REST. Il metodo withOverrides è anche type-safe. Ad esempio, se desideri sovrascrivere un’API getDocumentation per puntare alla documentazione ospitata da un sito web esterno, potresti ottenere questo come segue:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), }, }) .build(),});Noterai anche che l’integrazione sovrascritta non ha più una proprietà handler quando vi accedi tramite api.integrations.getDocumentation.
Puoi aggiungere proprietà aggiuntive a un’integrazione che saranno anche tipizzate di conseguenza, consentendo ad altri tipi di integrazione di essere astratti ma rimanere type-safe, ad esempio se hai creato un’integrazione S3 per un’API REST e successivamente desideri fare riferimento al bucket per una particolare operazione, puoi farlo come segue:
const storageBucket = new Bucket(this, 'Bucket', { ... });
const apiGatewayRole = new Role(this, 'ApiGatewayS3Role', { assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),});
storageBucket.grantRead(apiGatewayRole);
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getFile: { bucket: storageBucket, integration: new AwsIntegration({ service: 's3', integrationHttpMethod: 'GET', path: `${storageBucket.bucketName}/{fileName}`, options: { credentialsRole: apiGatewayRole, requestParameters: { 'integration.request.path.fileName': 'method.request.querystring.fileName', }, integrationResponses: [{ statusCode: '200' }], }, }), options: { requestParameters: { 'method.request.querystring.fileName': true, }, methodResponses: [{ statusCode: '200', }], } }, }) .build(),});
// Later, perhaps in another file, you can access the bucket property we defined// in a type-safe mannerapi.integrations.getFile.bucket.grantRead(...);Override degli Authorizer
Sezione intitolata “Override degli Authorizer”Puoi anche fornire options nella tua integrazione per sovrascrivere particolari opzioni del metodo come gli authorizer, ad esempio se desideri utilizzare l’autenticazione Cognito per la tua operazione getDocumentation:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), options: { authorizer: new CognitoUserPoolsAuthorizer(...) // for REST, or HttpUserPoolAuthorizer for an HTTP API } }, }) .build(),});Integrazioni Esplicite
Sezione intitolata “Integrazioni Esplicite”Se preferisci, puoi scegliere di non utilizzare le integrazioni predefinite e invece fornirne direttamente una per ogni operazione. Questo è utile se, ad esempio, ogni operazione deve utilizzare un tipo diverso di integrazione o desideri ricevere un errore di tipo quando aggiungi nuove operazioni:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});Per integrazioni esplicite per operazione con Terraform, dovresti modificare il modulo specifico dell’app generato per sostituire l’integrazione proxy predefinita con integrazioni specifiche per ogni operazione.
Modifica packages/common/terraform/src/app/apis/my-api/my-api.tf:
- Rimuovi le route proxy predefinite (ad es.,
resource "aws_apigatewayv2_route" "proxy_routes") - Sostituisci la singola funzione Lambda con funzioni individuali per ogni operazione
- Crea integrazioni e route specifiche per ogni operazione, riutilizzando lo stesso bundle ZIP:
# Remove the default single Lambda function 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 # ... rest of configuration }
# Remove the default proxy integration resource "aws_apigatewayv2_integration" "lambda_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.api_lambda.invoke_arn # ... rest of configuration }
# Remove the default proxy routes resource "aws_apigatewayv2_route" "proxy_routes" { for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]) api_id = module.http_api.api_id route_key = "${each.key} /{proxy+}" target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" # ... rest of configuration }
# Add individual Lambda functions for each operation using the same bundle resource "aws_lambda_function" "say_hello_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
# Add specific integrations for each operation resource "aws_apigatewayv2_integration" "say_hello_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.say_hello_handler.invoke_arn payload_format_version = "2.0" timeout_milliseconds = 30000 }
resource "aws_apigatewayv2_integration" "get_documentation_integration" { api_id = module.http_api.api_id integration_type = "HTTP_PROXY" integration_uri = "https://example.com/documentation" integration_method = "GET" }
# Add specific routes for each operation resource "aws_apigatewayv2_route" "say_hello_route" { api_id = module.http_api.api_id route_key = "POST /sayHello" target = "integrations/${aws_apigatewayv2_integration.say_hello_integration.id}" authorization_type = "AWS_IAM" }
resource "aws_apigatewayv2_route" "get_documentation_route" { api_id = module.http_api.api_id route_key = "GET /documentation" target = "integrations/${aws_apigatewayv2_integration.get_documentation_integration.id}" authorization_type = "NONE" }
# Add Lambda permissions for each function resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }# Remove the default single Lambda function resource "aws_lambda_function" "api_lambda" { 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 # ... rest of configuration }
# Remove the default proxy integration resource "aws_apigatewayv2_integration" "lambda_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.api_lambda.invoke_arn # ... rest of configuration }
# Remove the default proxy routes resource "aws_apigatewayv2_route" "proxy_routes" { for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]) api_id = module.http_api.api_id route_key = "${each.key} /{proxy+}" target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" # ... rest of configuration }
# Add individual Lambda functions for each operation using the same bundle resource "aws_lambda_function" "say_hello_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
# Add specific resources and methods for each operation resource "aws_api_gateway_resource" "say_hello_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "sayHello" }
resource "aws_api_gateway_method" "say_hello_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = "POST" authorization = "AWS_IAM" }
resource "aws_api_gateway_integration" "say_hello_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = aws_api_gateway_method.say_hello_method.http_method
integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.say_hello_handler.invoke_arn }
resource "aws_api_gateway_resource" "get_documentation_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "documentation" }
resource "aws_api_gateway_method" "get_documentation_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = "GET" authorization = "NONE" }
resource "aws_api_gateway_integration" "get_documentation_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = aws_api_gateway_method.get_documentation_method.http_method
integration_http_method = "GET" type = "HTTP" uri = "https://example.com/documentation" }
# Update deployment to depend on new integrations~ resource "aws_api_gateway_deployment" "api_deployment" { rest_api_id = module.rest_api.api_id
depends_on = [ aws_api_gateway_integration.lambda_integration, aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ]
lifecycle { create_before_destroy = true }
triggers = { redeployment = sha1(jsonencode([ aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ])) } }
# Add Lambda permissions for each function resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }Pattern di Integrazione
Sezione intitolata “Pattern di Integrazione”I costrutti API CDK generati supportano due pattern di integrazione:
isolatedcrea una funzione Lambda per operazione. Questo è il valore predefinito per le API generate.sharedcrea un singolo router Lambda predefinito e lo riutilizza per ogni operazione a meno che non si sovrascrivano integrazioni specifiche.
isolated ti offre autorizzazioni e configurazione più granulari per operazione. shared riduce la proliferazione di Lambda e integrazioni API Gateway pur consentendo override selettivi.
Ad esempio, impostando pattern su 'shared' si crea una singola funzione invece di una per integrazione:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { ... return IntegrationBuilder.rest({ pattern: 'shared', ... }); };}I moduli Terraform utilizzano automaticamente il pattern router - questo è l’approccio predefinito e unico supportato. Il modulo generato crea una singola funzione Lambda che gestisce tutte le operazioni API.
Puoi semplicemente istanziare il modulo predefinito per ottenere il pattern router:
# Default router pattern - single Lambda function for all operationsmodule "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Single Lambda function handles all operations automatically tags = local.common_tags}Generazione del codice
Sezione intitolata “Generazione del codice”Poiché le operazioni in FastAPI sono definite in Python e l’infrastruttura CDK in TypeScript, strumentiamo la generazione del codice per fornire metadati al costrutto CDK per fornire un’interfaccia type-safe per le integrazioni.
Un target generate:<ApiName>-metadata viene aggiunto al project.json dei costrutti comuni per facilitare questa generazione di codice, che emette un file come packages/common/constructs/src/generated/my-api/metadata.gen.ts. Poiché questo viene generato al momento della build, viene ignorato nel controllo di versione.
Concessione dell’accesso (solo IAM)
Sezione intitolata “Concessione dell’accesso (solo IAM)”Se hai scelto di utilizzare l’autenticazione IAM, puoi utilizzare il metodo grantInvokeAccess per concedere l’accesso alla tua API:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);# Create an IAM policy to allow invoking the APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Policy to allow invoking the FastAPI"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Attach the policy to an IAM role (e.g., for authenticated users)resource "aws_iam_role_policy_attachment" "api_invoke_access" { role = aws_iam_role.authenticated_user_role.name policy_arn = aws_iam_policy.api_invoke_policy.arn}
# Or attach to an existing role by nameresource "aws_iam_role_policy_attachment" "api_invoke_access_existing" { role = "MyExistingRole" policy_arn = aws_iam_policy.api_invoke_policy.arn}Gli output chiave del modulo API che puoi utilizzare per le policy IAM sono:
module.my_api.api_execution_arn- Per concedere permessi execute-api:Invokemodule.my_api.api_arn- L’ARN di API Gatewaymodule.my_api.lambda_function_arn- L’ARN della funzione Lambda
Sviluppo locale
Sezione intitolata “Sviluppo locale”Il generatore configura un server di sviluppo locale che puoi eseguire con:
pnpm nx serve my-apiyarn nx serve my-apinpx nx serve my-apibunx nx serve my-apiQuesto avvia un server di sviluppo FastAPI locale con:
- Auto-reload sulle modifiche del codice
- Documentazione API interattiva su
/docso/redoc - Schema OpenAPI su
/openapi.json
Invocare la tua FastAPI
Sezione intitolata “Invocare la tua FastAPI”Per invocare la tua API da un sito web React, puoi utilizzare il generatore connection.
Connessioni
Sezione intitolata “Connessioni”Usa il generatore connection per integrare questo progetto con altri nel tuo workspace. Le seguenti connessioni coinvolgono questo progetto: