FastAPI
FastAPI est un framework pour créer des API en Python.
Le générateur FastAPI crée une nouvelle FastAPI avec une configuration d’infrastructure AWS CDK ou Terraform. Le backend généré utilise AWS Lambda pour un déploiement serverless, exposé via une API AWS API Gateway. Il configure AWS Lambda Powertools pour l’observabilité, incluant la journalisation, le traçage AWS X-Ray et les métriques Cloudwatch.
Utilisation
Section intitulée « Utilisation »Générer une FastAPI
Section intitulée « Générer une FastAPI »Vous pouvez générer une nouvelle FastAPI de deux manières :
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=fastapiVous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
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- Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
- Ouvrez la console Nx dans VSCode
- Cliquez sur
Generate (UI)dans la section "Common Nx Commands" - Recherchez
@aws/nx-plugin - py#api - Remplissez les paramètres requis
- framework: fastapi
- Cliquez sur
Generate
| Paramètre | Type | Par défaut | Description |
|---|---|---|---|
| name Requis | string | - | Nom du projet API à générer |
| framework | fastapi | fastapi | Le framework d'API à utiliser. |
| integrationPattern | isolated | shared | isolated | Comment les intégrations API Gateway sont générées pour l'API. Choisissez entre isolated (par défaut) et shared. |
| auth | iam | cognito | custom | iam | La méthode utilisée pour s'authentifier auprès de votre API. Choisissez entre iam (par défaut), cognito ou custom. |
| directory | string | packages | Le répertoire dans lequel stocker l'application. |
| subDirectory | string | - | Le sous-répertoire dans lequel le projet est placé. Par défaut, il s'agit du nom du projet. |
| iac | inherit | cdk | terraform | inherit | Le fournisseur IaC préféré. Par défaut, celui-ci est hérité de votre sélection initiale. |
| moduleName | string | - | Nom du module Python |
| infra | rest-lambda | http-lambda | none | rest-lambda | Le type d'infrastructure à utiliser pour déployer cette API. |
| preferInstallDependencies | boolean | true | Indique s'il faut privilégier l'installation des dépendances après l'exécution du générateur. Définir sur false pour différer l'installation lors de l'exécution de plusieurs générateurs en lot (une installation s'exécute quand même si nécessaire pour que les générateurs suivants puissent calculer le graphe de projet Nx) ; installer une seule fois à la fin. |
Sortie du générateur
Section intitulée « Sortie du générateur »Le générateur créera la structure de projet suivante dans le répertoire <directory>/<api-name> :
- project.json Configuration du projet et cibles de build
- pyproject.toml Configuration et dépendances du projet Python
- run.sh Script de démarrage Lambda Web Adapter pour démarrer l’application FastAPI via uvicorn
Répertoire<module_name>
- __init__.py Initialisation du module
- init.py Configure l’application FastAPI et configure le middleware powertools
- main.py Implémentation de l’API
Répertoirescripts
- generate_open_api.py Script pour générer un schéma OpenAPI à partir de l’application FastAPI
Infrastructure
Section intitulée « Infrastructure »Étant donné que ce générateur fournit de l’infrastructure en tant que code basée sur votre iac choisi, il créera un projet dans packages/common qui inclut les constructs CDK ou modules Terraform pertinents.
Le projet d’infrastructure en tant que code commun est structuré comme suit :
Répertoirepackages/common/constructs
Répertoiresrc
Répertoireapp/ Constructs pour l’infrastructure spécifique à un projet/générateur
- …
Répertoirecore/ Constructs génériques qui sont réutilisés par les constructs dans
app- …
- index.ts Entry point exporting constructs from
app
- project.json Project build targets and configuration
Répertoirepackages/common/terraform
Répertoiresrc
Répertoireapp/ Terraform modules for infrastructure specific to a project/generator
- …
Répertoirecore/ Generic modules which are reused by modules in
app- …
- project.json Project build targets and configuration
Pour déployer votre API, les fichiers suivants sont générés :
Répertoirepackages/common/constructs/src
Répertoireapp
Répertoireapis
- <project-name>.ts CDK construct for deploying your API
Répertoirecore
Répertoireapi
- 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
Répertoirepackages/common/terraform/src
Répertoireapp
Répertoireapis
Répertoire<project-name>
- <project-name>.tf Module for deploying your API
Répertoirecore
Répertoireapi
Répertoirehttp-api
- http-api.tf Module for deploying an HTTP API (if you selected to deploy an HTTP API)
Répertoirerest-api
- rest-api.tf Module for deploying a REST API (if you selected to deploy a REST API)
Architecture
Section intitulée « Architecture »L’application déployée a l’architecture suivante :
Les REST APIs incluent une Web ACL AWS WAFv2 devant l’étape API Gateway avec l’ensemble de règles par défaut géré par AWS activé.
Les HTTP APIs ne prennent pas en charge WAF directement — si vous avez besoin de la protection WAF, choisissez REST API à la place ou placez l’HTTP API derrière une distribution CloudFront.
Implémenter votre FastAPI
Section intitulée « Implémenter votre FastAPI »L’implémentation principale de l’API se trouve dans main.py. C’est ici que vous définissez vos routes d’API et leurs implémentations. Voici un exemple :
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 ...Le générateur configure automatiquement plusieurs fonctionnalités :
- Intégration d’AWS Lambda Powertools pour l’observabilité
- Middleware de gestion des erreurs
- Corrélation requête/réponse
- Collection de métriques
- Déploiement AWS Lambda via Lambda Web Adapter avec uvicorn
- Streaming type-safe (API REST uniquement)
Observabilité avec AWS Lambda Powertools
Section intitulée « Observabilité avec AWS Lambda Powertools »Journalisation
Section intitulée « Journalisation »Le générateur configure la journalisation structurée en utilisant AWS Lambda Powertools. Vous pouvez accéder au logger dans vos gestionnaires de routes :
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}Le logger inclut automatiquement :
- Les ID de corrélation pour le traçage des requêtes
- Le chemin et la méthode de la requête
- Les informations de contexte Lambda
- Les indicateurs de démarrage à froid
Le traçage AWS X-Ray est configuré automatiquement. Vous pouvez ajouter des sous-segments personnalisés à vos traces :
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}Métriques
Section intitulée « Métriques »Les métriques CloudWatch sont collectées automatiquement pour chaque requête. Vous pouvez ajouter des métriques personnalisées :
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}Les métriques par défaut incluent :
- Nombre de requêtes
- Nombre de succès/échecs
- Métriques de démarrage à froid
- Métriques par route
Gestion des erreurs
Section intitulée « Gestion des erreurs »Le générateur inclut une gestion complète des erreurs :
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}Les exceptions non gérées sont capturées par le middleware et :
- Enregistrent l’exception complète avec la trace de pile
- Enregistrent une métrique d’échec
- Retournent une réponse 500 sécurisée au client
- Préservent l’ID de corrélation
Accéder à l’utilisateur appelant
Section intitulée « Accéder à l’utilisateur appelant »Lorsque votre API est protégée par authentification, vos gestionnaires de routes ont souvent besoin de savoir qui appelle. La FastAPI générée s’exécute dans AWS Lambda via le Lambda Web Adapter, qui transmet le contexte de requête API Gateway sous forme de JSON dans l’en-tête x-amzn-request-context. Vous pouvez le lire depuis la Request FastAPI pour extraire l’identité de l’appelant.
Par exemple, ajoutons un point de terminaison /me qui retourne les détails de l’utilisateur appelant. Nous implémenterons l’extraction comme une dépendance FastAPI afin qu’elle puisse être réutilisée entre les routes. La forme du contexte de requête — et donc comment vous extrayez l’identité — dépend à la fois de votre méthode auth sélectionnée et du fait que vous ayez déployé une API REST ou HTTP.
Pour l’authentification IAM, nous recherchons l’appelant dans Cognito en utilisant le sub extrait du contexte de requête API Gateway. Créez identity.py à côté 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: # 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)]Avec auth: 'cognito', l’autorisateur Cognito User Pools d’API Gateway vérifie le JWT que l’appelant fournit dans l’en-tête Authorization et place les revendications vérifiées dans le contexte de requête.
Créez identity.py à côté 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: # 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)]Les API HTTP utilisent un autorisateur JWT qui place les revendications vérifiées sous 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)]Vous pouvez ensuite injecter la dépendance CurrentUser dans n’importe quelle route qui a besoin de l’identité de l’appelant :
from .identity import CurrentUser, Identityfrom .init import app, tracer
@app.get("/me")@tracer.capture_methoddef me(identity: CurrentUser) -> Identity: return identityStreaming
Section intitulée « Streaming »La FastAPI générée prend en charge les réponses en streaming dès le départ lors de l’utilisation d’une API REST. L’infrastructure est configurée pour utiliser le AWS Lambda Web Adapter pour exécuter votre FastAPI via uvicorn dans Lambda, avec ResponseTransferMode.STREAM dans API Gateway pour toutes les opérations d’API REST, ce qui permet au streaming de fonctionner aux côtés d’opérations non-streaming.
Utilisation de JsonStreamingResponse
Section intitulée « Utilisation de JsonStreamingResponse »Le fichier init.py généré exporte une classe JsonStreamingResponse qui fournit un streaming type-safe avec une génération de schéma OpenAPI appropriée. Cela garantit que le générateur connection peut produire des méthodes client de streaming correctement typées.
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 :
- Sérialise les modèles Pydantic au format JSON Lines (
application/jsonl) - Fournit un helper
openapi_responsequi génère le schéma OpenAPI correct avecitemSchema, permettant au générateurconnectionde produire des méthodes client de streaming type-safe
Consommation
Section intitulée « Consommation »Pour consommer un flux de réponses, vous pouvez utiliser le générateur connection qui fournira une méthode type-safe pour itérer sur vos chunks streamés.
Déployer votre FastAPI
Section intitulée « Déployer votre FastAPI »Le générateur FastAPI crée une infrastructure en tant que code CDK ou Terraform en fonction de votre iac sélectionné. Vous pouvez l’utiliser pour déployer votre FastAPI.
Le construct CDK pour déployer votre API dans le dossier common/constructs. Vous pouvez l’utiliser dans une application 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(), }); }}Cela configure :
- Une fonction AWS Lambda pour chaque opération dans l’application FastAPI
- API Gateway HTTP/REST API comme déclencheur de fonction
- Rôles et permissions IAM
- Groupe de logs CloudWatch
- Configuration du traçage X-Ray
- Espace de noms de métriques CloudWatch
Les modules Terraform pour déployer votre API se trouvent dans le dossier common/terraform. Vous pouvez les utiliser dans une configuration Terraform.
Le module API met en scène son zip de déploiement Lambda dans un bucket S3 d’actifs partagé — voir le guide d’infrastructure Terraform pour plus de détails. Instanciez le module core/asset-bucket une fois par déploiement et passez sa sortie bucket_name dans chaque module API / Lambda via l’entrée 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}Cela configure :
- Une fonction AWS Lambda qui sert toutes les routes FastAPI
- API Gateway HTTP/REST API comme déclencheur de fonction
- Rôles et permissions IAM
- Groupe de logs CloudWatch
- Configuration du traçage X-Ray
- Configuration CORS
Le module Terraform fournit plusieurs sorties que vous pouvez utiliser :
# 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}Vous pouvez personnaliser les paramètres CORS en passant des variables au module :
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}Pour les API REST, le construct généré associe par défaut une Web ACL AWS WAFv2 à l’étape API Gateway. La Web ACL utilise l’ensemble de règles par défaut géré par AWS (AWSManagedRulesCommonRuleSet et AWSManagedRulesKnownBadInputsRuleSet), offrant une protection contre les exploits web courants, y compris le Top 10 OWASP. Les journaux de requêtes WAF sont écrits dans un groupe CloudWatch Logs.
Vous pouvez modifier le construct rest-api généré pour ajouter, supprimer ou ajuster des règles (par exemple, pour ajouter des règles basées sur le taux ou des groupes de règles gérées supplémentaires).
Pour désactiver cette fonctionnalité (par exemple, pour attacher votre propre Web ACL), définissez enableWaf sur false :
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), enableWaf: false,});Pour désactiver cette fonctionnalité (par exemple, pour attacher votre propre Web ACL), définissez enable_waf sur false :
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name enable_waf = false}Journalisation des accès
Section intitulée « Journalisation des accès »Pour les API REST, l’infrastructure générée active la journalisation des accès par défaut, écrivant une ligne JSON structurée par requête dans un groupe CloudWatch Logs dédié. Le groupe de logs est chiffré avec une clé KMS gérée par le client et conservé pendant un an.
API Gateway écrit les logs d’accès en utilisant un rôle CloudWatch Logs au niveau du compte. Ce rôle est configuré sur le paramètre AWS::ApiGateway::Account, qui est un singleton par région par compte — il n’y a qu’un seul rôle pour chaque API REST dans la région. Pour gérer cela en toute sécurité à travers plusieurs stacks déployées indépendamment, l’infrastructure générée :
- Crée un rôle CloudWatch Logs partagé et le configure sur le compte uniquement lorsqu’aucun rôle fonctionnel n’est déjà défini, de sorte que les déploiements n’écrasent jamais un rôle appartenant à une autre stack.
- Laisse le paramètre du compte intact lors du démontage, de sorte que la destruction d’une stack ne désactive jamais la journalisation pour d’autres API REST dans la région.
Le rôle du compte est géré par le construct ApiGatewayAccount, un singleton à portée de stack résolu via ApiGatewayAccount.ensure(scope). Le stage de chaque API REST en dépend, et le rôle est configuré par une ressource personnalisée basée sur Lambda.
Vous pouvez personnaliser le format du log d’accès en passant deployOptions lors de la construction de votre API :
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), deployOptions: { accessLogFormat: AccessLogFormat.clf(), },});Le rôle du compte est géré par le module core/api/api-gateway-account, qui est instancié par le module API généré. Il configure le compte de manière idempotente et n’est jamais réinitialisé lors d’un terraform destroy.
Vous pouvez personnaliser le format du log d’accès en modifiant le bloc access_log_settings sur la ressource aws_api_gateway_stage dans le module API généré.
Intégrations
Section intitulée « Intégrations »Les constructs CDK d’API REST/HTTP sont configurés pour fournir une interface type-safe pour définir des intégrations pour chacune de vos opérations.
Les constructs CDK fournissent un support complet d’intégration type-safe comme décrit ci-dessous.
Intégrations par défaut
Section intitulée « Intégrations par défaut »Vous pouvez utiliser la méthode statique defaultIntegrations pour utiliser le modèle par défaut, qui définit une fonction AWS Lambda individuelle pour chaque opération :
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});Les modules Terraform utilisent automatiquement le modèle de routeur avec une seule fonction Lambda. Aucune configuration supplémentaire n’est nécessaire :
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}Accès aux intégrations
Section intitulée « Accès aux intégrations »Vous pouvez accéder aux fonctions AWS Lambda sous-jacentes via la propriété integrations du construct API, de manière type-safe. Par exemple, si votre API définit une opération nommée sayHello et que vous devez ajouter des permissions à cette fonction, vous pouvez le faire comme suit :
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: [...],}));Si votre API utilise le modèle shared, le Lambda de routeur partagé est exposé comme api.integrations.$router :
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');Avec le modèle de routeur de Terraform, il n’y a qu’une seule fonction Lambda. Vous pouvez y accéder via les sorties du module :
# 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/*" } ] })}Personnalisation des options par défaut
Section intitulée « Personnalisation des options par défaut »Si vous souhaitez personnaliser les options utilisées lors de la création de la fonction Lambda pour chaque intégration par défaut, vous pouvez utiliser la méthode withDefaultOptions. Par exemple, si vous souhaitez que toutes vos fonctions Lambda résident dans un Vpc :
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});Pour personnaliser des options comme la configuration VPC, vous devez modifier le module Terraform généré. Par exemple, pour ajouter le support VPC à toutes les fonctions 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 }}Ensuite, utilisez le module avec la configuration 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}Personnalisation des options par opération
Section intitulée « Personnalisation des options par opération »Pour personnaliser les options utilisées pour créer l’intégration par défaut pour des opérations spécifiques (sans affecter les autres), vous pouvez utiliser la méthode withOperationOptions. Par exemple, si vous souhaitez augmenter le timeout de la fonction Lambda pour une seule opération :
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({ ... }));Les options que vous spécifiez sont fusionnées avec les options d’intégration par défaut (et toutes les options définies via withDefaultOptions). Notez que vous ne pouvez pas spécifier d’options pour les opérations que vous avez remplacées via withOverrides, car celles-ci n’utilisent plus l’intégration par défaut.
Vous rencontrerez une erreur de type si la même opération est ciblée à la fois par withOperationOptions et withOverrides, quel que soit l’ordre dans lequel vous les appelez.
Pour personnaliser les options pour des opérations spécifiques avec Terraform, vous devez modifier le module Terraform généré pour configurer des fonctions Lambda individuelles par opération (voir la section Intégrations explicites ci-dessous).
Remplacement des intégrations
Section intitulée « Remplacement des intégrations »Vous pouvez également remplacer les intégrations pour des opérations spécifiques en utilisant la méthode withOverrides. Chaque remplacement doit spécifier une propriété integration qui est typée selon le construct d’intégration CDK approprié pour l’API HTTP ou REST. La méthode withOverrides est également type-safe. Par exemple, si vous souhaitez remplacer une API getDocumentation pour pointer vers de la documentation hébergée par un site web externe, vous pourriez le faire comme suit :
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), }, }) .build(),});Vous remarquerez également que l’intégration remplacée n’a plus de propriété handler lors de l’accès via api.integrations.getDocumentation.
Vous pouvez ajouter des propriétés supplémentaires à une intégration qui seront également typées en conséquence, permettant à d’autres types d’intégration d’être abstraits tout en restant type-safe, par exemple si vous avez créé une intégration S3 pour une API REST et souhaitez plus tard référencer le bucket pour une opération particulière, vous pouvez le faire comme suit :
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(...);Remplacement des autorisateurs
Section intitulée « Remplacement des autorisateurs »Vous pouvez également fournir des options dans votre intégration pour remplacer des options de méthode particulières telles que les autorisateurs, par exemple si vous souhaitez utiliser l’authentification Cognito pour votre opération 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(),});Intégrations explicites
Section intitulée « Intégrations explicites »Si vous préférez, vous pouvez choisir de ne pas utiliser les intégrations par défaut et d’en fournir directement une pour chaque opération. Ceci est utile si, par exemple, chaque opération doit utiliser un type d’intégration différent ou si vous souhaitez recevoir une erreur de type lors de l’ajout de nouvelles opérations :
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});Pour des intégrations explicites par opération avec Terraform, vous devez modifier le module spécifique à l’application généré pour remplacer l’intégration proxy par défaut par des intégrations spécifiques pour chaque opération.
Modifiez packages/common/terraform/src/app/apis/my-api/my-api.tf :
- Supprimez les routes proxy par défaut (par exemple,
resource "aws_apigatewayv2_route" "proxy_routes") - Remplacez la fonction Lambda unique par des fonctions individuelles pour chaque opération
- Créez des intégrations et routes spécifiques pour chaque opération, en réutilisant le même 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}/*/*" }Modèle d’intégration
Section intitulée « Modèle d’intégration »Les constructs d’API CDK générés prennent en charge deux modèles d’intégration :
isolatedcrée une fonction Lambda par opération. C’est le modèle par défaut pour les API générées.sharedcrée un seul Lambda de routeur par défaut et le réutilise pour chaque opération sauf si vous remplacez des intégrations spécifiques.
isolated vous donne des permissions et une configuration plus granulaires par opération. shared réduit la prolifération des fonctions Lambda et des intégrations API Gateway tout en permettant des remplacements sélectifs.
Par exemple, définir pattern à 'shared' crée une seule fonction au lieu d’une par intégration :
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { ... return IntegrationBuilder.rest({ pattern: 'shared', ... }); };}Les modules Terraform utilisent automatiquement le modèle de routeur - c’est l’approche par défaut et la seule prise en charge. Le module généré crée une seule fonction Lambda qui gère toutes les opérations API.
Vous pouvez simplement instancier le module par défaut pour obtenir le modèle de routeur :
# 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}Génération de code
Section intitulée « Génération de code »Étant donné que les opérations dans FastAPI sont définies en Python et l’infrastructure CDK en TypeScript, nous instrumentons la génération de code pour fournir des métadonnées au construct CDK afin de fournir une interface type-safe pour les intégrations.
Une cible generate:<ApiName>-metadata est ajoutée au project.json des constructs communs pour faciliter cette génération de code, qui émet un fichier tel que packages/common/constructs/src/generated/my-api/metadata.gen.ts. Puisque cela est généré au moment du build, il est ignoré dans le contrôle de version.
Accorder l’accès (IAM uniquement)
Section intitulée « Accorder l’accès (IAM uniquement) »Si vous avez choisi d’utiliser l’authentification IAM, vous pouvez utiliser la méthode grantInvokeAccess pour accorder l’accès à votre 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}Les sorties clés du module API que vous pouvez utiliser pour les politiques IAM sont :
module.my_api.api_execution_arn- Pour accorder les permissions execute-api:Invokemodule.my_api.api_arn- L’ARN API Gatewaymodule.my_api.lambda_function_arn- L’ARN de la fonction Lambda
Développement local
Section intitulée « Développement local »Le générateur configure un serveur de développement local que vous pouvez exécuter avec :
pnpm nx serve my-apiyarn nx serve my-apinpx nx serve my-apibunx nx serve my-apiCela démarre un serveur de développement FastAPI local avec :
- Rechargement automatique lors des modifications de code
- Documentation API interactive sur
/docsou/redoc - Schéma OpenAPI sur
/openapi.json
Invoquer votre FastAPI
Section intitulée « Invoquer votre FastAPI »Pour invoquer votre API depuis un site web React, vous pouvez utiliser le générateur connection.
Connexions
Section intitulée « Connexions »Utilisez le générateur connection pour intégrer ce projet avec d’autres dans votre espace de travail. Les connexions suivantes impliquent ce projet :