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 :
Exécuter ce générateur@aws/nx-plugin:py#api
pnpm nx g @aws/nx-plugin:py#api yarn nx g @aws/nx-plugin:py#api npx nx g @aws/nx-plugin:py#api bunx nx g @aws/nx-plugin:py#api- 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
- Cliquez sur
Generate
Composez votre commande10
Requis
nameRequisstringNom du projet API à générer
frameworkenumPar défaut:fastapiLe framework d'API à utiliser.
fastapiintegrationPatternenumPar défaut:isolatedComment les intégrations API Gateway sont générées pour l'API. Choisissez entre isolated (par défaut) et shared.
isolatedsharedauthenumPar défaut:iamLa méthode utilisée pour s'authentifier auprès de votre API. Choisissez entre iam (par défaut), cognito ou custom.
iamcognitocustomdirectorystringPar défaut:packagesLe répertoire dans lequel stocker l'application.
iacenumPar défaut:inheritLe fournisseur IaC préféré. Par défaut, celui-ci est hérité de votre sélection initiale.
inheritcdkterraforminfraenumPar défaut:rest-lambdaLe type d'infrastructure à utiliser pour déployer cette API.
rest-lambdahttp-lambdanonesubDirectorystringLe sous-répertoire dans lequel le projet est placé. Par défaut, il s'agit du nom du projet.
moduleNamestringNom du module Python
preferInstallDependenciesbooleanPar défaut:trueIndique 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 : une API API Gateway devant une fonction Lambda exécutant votre gestionnaire.
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=f"Item {item_id}")
@app.post("/items")@tracer.capture_methoddef create_item(item: Item) -> Item: return itemLe 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 de la requête, la route correspondante et la méthode
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 par route (via une dimension
routede<method> <path>)
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.
Le format du log d’accès est défini par le construct RestApi que votre API étend. Pour le personnaliser, passez deployOptions à super dans le fichier généré packages/common/constructs/src/app/apis/my-api.ts, en conservant le tracingEnabled que le construct définit déjà :
super(scope, id, { apiName: 'MyApi', // ... deployOptions: { tracingEnabled: true, accessLogFormat: AccessLogFormat.clf(), }, ...props,});AccessLogFormat est importé depuis aws-cdk-lib/aws-apigateway. Tout ce que vous ne définissez pas conserve la valeur par défaut du construct — un format JSON avec les champs standard.
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.
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(),});Le module généré définit déjà les intégrations par défaut pour le modèle avec lequel l’API a été générée, donc 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
tags = local.common_tags}Avec le modèle isolated par défaut, cela crée une fonction Lambda par opération.
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');Notez que $router n’est plus disponible si vous remplacez chaque opération via withOverrides, puisqu’aucune opération n’utilise plus l’intégration de routeur par défaut.
Avec le modèle isolated, les sorties du module sont des maps indexées par nom d’opération, vous pouvez donc accéder aux ressources d’une seule opération. Par exemple, pour accorder des permissions supplémentaires à la fonction Lambda d’une opération :
# Grant additional permissions to just the sayHello operation's functionresource "aws_iam_role_policy" "say_hello_permissions" { name = "say-hello-additional-permissions" role = module.my_api.lambda_execution_role_names["sayHello"]
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject" ] Resource = "arn:aws:s3:::my-bucket/*" } ] })}Pour accorder les mêmes permissions à toutes les opérations, itérez sur la sortie operations :
resource "aws_iam_role_policy" "additional_permissions" { for_each = toset(module.my_api.operations)
name = "additional-api-permissions" role = module.my_api.lambda_execution_role_names[each.key]
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["s3:GetObject"] Resource = "arn:aws:s3:::my-bucket/*" } ] })}Le module expose également lambda_function_names, lambda_function_arns, lambda_invoke_arns, integration_ids et lambda_log_group_names comme des maps indexées par nom d’opération. Avec le modèle shared, les sorties singulières équivalentes (lambda_execution_role_name, lambda_function_name, …) sont exposées à la place, puisqu’il n’y a qu’une seule fonction.
Les permissions dont chaque opération a besoin sont mieux passées au module, qui les applique au rôle de chaque fonction :
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
additional_iam_policy_statements = [ { Effect = "Allow" Action = ["s3:GetObject"] 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(),});La configuration VPC est déjà prise en charge par le module généré — définissez enable_vpc avec vpc_id et subnet_ids, et le module déploie chaque fonction Lambda dans votre VPC derrière un groupe de sécurité partagé qu’il crée pour vous :
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# VPC configuration enable_vpc = true vpc_id = aws_vpc.main.id subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id]
tags = local.common_tags}Pour les options que le module n’expose pas, modifiez directement la ressource aws_lambda_function dans le module Terraform généré. Avec le modèle isolated, cette ressource unique est déclarée for_each = local.operations, donc une modification s’applique à toutes les opérations.
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.
Avec le modèle isolated, la ressource de fonction Lambda est déjà par opération, donc les options peuvent varier selon le nom de l’opération. Par exemple, pour donner à une opération un timeout plus long, modifiez la ressource aws_lambda_function dans le module généré :
resource "aws_lambda_function" "api_lambda" { for_each = local.operations
# Default to 30 seconds, but allow longer for specific operations timeout = lookup({ sayHello = 60 }, each.key, 30)
# ... rest of configuration}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(...);Pour pointer une opération spécifique vers un type d’intégration différent, excluez-la du for_each par défaut et déclarez son intégration séparément. Par exemple, pour servir getDocumentation depuis un site web externe :
# Exclude the overridden operation from the default per-operation resourceslocals { overridden_operations = ["getDocumentation"] default_operations = { for op, details in local.operations : op => details if !contains(local.overridden_operations, op) }}
# Then use local.default_operations in place of local.operations for the# aws_lambda_function, aws_iam_role, aws_apigatewayv2_integration and# aws_lambda_permission resources, and add the override:resource "aws_apigatewayv2_integration" "get_documentation" { api_id = module.http_api.api_id integration_type = "HTTP_PROXY" integration_uri = "https://example.com/documentation" integration_method = "GET"}
resource "aws_apigatewayv2_route" "get_documentation" { api_id = module.http_api.api_id route_key = local.route_key["getDocumentation"] target = "integrations/${aws_apigatewayv2_integration.get_documentation.id}"}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(),});L’autorisation est définie sur la route (API HTTP) ou la méthode (API REST) pour chaque opération, elle peut donc varier selon le nom de l’opération. Par exemple, pour laisser une opération non authentifiée sur une API HTTP :
resource "aws_apigatewayv2_route" "operation_routes" { for_each = local.operations
# ... rest of configuration
authorization_type = each.key == "getDocumentation" ? "NONE" : "AWS_IAM"}Pour une API REST authentifiée par IAM, ajoutez également une déclaration de politique de ressource autorisant l’accès non authentifié au chemin de cette opération.
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(...), }, },});Remplacez le for_each utilisé par le modèle isolated par des instanciations explicites des fonctions Lambda, intégrations et permissions pour chaque opération.
Modèle d’intégration
Section intitulée « Modèle d’intégration »Les API générées prennent en charge deux modèles d’intégration :
isolatedcrée une fonction Lambda par opération. C’est l’option par défaut et recommandée pour les API.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, ainsi qu’une meilleure séparation pour les logs et les traces. shared réduit la probabilité de rencontrer des démarrages à froid pour les API à faible utilisation.
Le modèle d’intégration peut être changé à tout moment dans CDK en mettant à jour votre construct API. 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', ... }); };}Contrairement à CDK, le modèle d’intégration est intégré dans le module généré. Pour changer le modèle d’intégration :
- Supprimez le module API précédemment généré dans
packages/common/terraform/src/app/apis - Relancez le générateur qui a créé votre API avec l’autre modèle d’intégration (par exemple
--integrationPattern=shared)
Avec le modèle isolated, le module lit les opérations depuis un fichier généré :
locals { operations_file = "${path.module}/../../../generated/my-api/operations.json" operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}}Ce fichier est généré à partir de votre API, vous n’avez donc pas besoin de le modifier manuellement. L’ajout d’une opération à votre code d’application API ajoute la route et la fonction lambda lors du prochain déploiement. Il est .gitignored par défaut ; supprimez l’entrée si vous préférez le versionner.
Limite de profondeur de chemin de l’API REST Terraform
Section intitulée « Limite de profondeur de chemin de l’API REST Terraform »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 <project-name>yarn nx serve <project-name>npx nx serve <project-name>bunx nx serve <project-name>Cela 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 :