FastAPI
FastAPI est un framework pour construire des APIs en Python.
Le générateur FastAPI crée une nouvelle application FastAPI avec une 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 tracing AWS X-Ray et les métriques CloudWatch.
Utilisation
Section intitulée « Utilisation »Générer une API FastAPI
Section intitulée « Générer une API FastAPI »Vous pouvez générer une nouvelle API FastAPI de deux manières :
- 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#fast-api
- Remplissez les paramètres requis
- Cliquez sur
Generate
pnpm nx g @aws/nx-plugin:py#fast-api
yarn nx g @aws/nx-plugin:py#fast-api
npx nx g @aws/nx-plugin:py#fast-api
bunx nx g @aws/nx-plugin:py#fast-api
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
pnpm nx g @aws/nx-plugin:py#fast-api --dry-run
yarn nx g @aws/nx-plugin:py#fast-api --dry-run
npx nx g @aws/nx-plugin:py#fast-api --dry-run
bunx nx g @aws/nx-plugin:py#fast-api --dry-run
Paramètre | Type | Par défaut | Description |
---|---|---|---|
name Requis | string | - | Name of the API project to generate |
computeType | string | ServerlessApiGatewayRestApi | The type of compute to use to deploy this API. Choose between ServerlessApiGatewayRestApi (default) or ServerlessApiGatewayHttpApi. |
auth | string | IAM | The method used to authenticate with your API. Choose between IAM (default), Cognito or None. |
directory | string | packages | The directory to store the application in. |
iacProvider | string | CDK | The preferred IaC provider |
moduleName | string | - | Python module name |
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 du projet Python et dépendances
Répertoire<module_name>
- __init__.py Initialisation du module
- init.py Configure l’application FastAPI et 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 depuis l’app FastAPI
Infrastructure
Section intitulée « Infrastructure »Ce générateur fournit de l’infrastructure as code basée sur votre iacProvider
choisi. Il créera un projet dans packages/common
qui inclut les constructions CDK ou modules Terraform pertinents.
Le projet commun d’infrastructure as code est structuré comme suit :
Répertoirepackages/common/constructs
Répertoiresrc
Répertoireapp/ Constructions pour l’infrastructure spécifique à un projet/générateur
- …
Répertoirecore/ Constructions génériques réutilisées par celles dans
app
- …
- index.ts Point d’entrée exportant les constructions depuis
app
- project.json Cibles de build et configuration du projet
Répertoirepackages/common/terraform
Répertoiresrc
Répertoireapp/ Modules Terraform pour l’infrastructure spécifique à un projet/générateur
- …
Répertoirecore/ Modules génériques réutilisés par ceux dans
app
- …
- project.json Cibles de build et configuration du projet
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 Construct CDK pour déployer votre API
Répertoirecore
Répertoireapi
- http-api.ts Construct CDK pour déployer une API HTTP (si vous avez choisi de déployer une API HTTP)
- rest-api.ts Construct CDK pour déployer une API REST (si vous avez choisi de déployer une API REST)
- utils.ts Utilitaires pour les constructs d’API
Répertoirepackages/common/terraform/src
Répertoireapp
Répertoireapis
Répertoire<project-name>
- <project-name>.tf Module pour déployer votre API
Répertoirecore
Répertoireapi
Répertoirehttp-api
- http-api.tf Module pour déployer une API HTTP (si vous avez choisi de déployer une API HTTP)
Répertoirerest-api
- rest-api.tf Module pour déployer une API REST (si vous avez choisi de déployer une API REST)
Implémentation de votre API FastAPI
Section intitulée « Implémentation de votre API FastAPI »L’implémentation principale de l’API se trouve dans main.py
. C’est ici que vous définissez vos routes API et leurs implémentations. Voici un exemple :
from .init import app, tracerfrom pydantic import BaseModel
class Item(BaseModel): name: str
@app.get("/items/{item_id}")def get_item(item_id: int) -> Item: return Item(name=...)
@app.post("/items")def 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 d’erreurs
- Corrélation requête/réponse
- Collecte de métriques
- Handler AWS Lambda utilisant Mangum
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 avec AWS Lambda Powertools. Vous pouvez accéder au logger dans vos gestionnaires de 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}
Le logger inclut automatiquement :
- Des IDs de corrélation pour le tracing des requêtes
- Le chemin et la méthode de la requête
- Les informations de contexte Lambda
- Les indicateurs de cold start
Le tracing 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): # Crée un nouveau sous-segment with tracer.provider.in_subsegment("fetch-item-details"): # Votre logique ici 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 :
- Le nombre de requêtes
- Les compteurs de succès/échec
- Les métriques de cold start
- Les métriques par route
Gestion des erreurs
Section intitulée « Gestion des erreurs »Le générateur inclut une gestion d’erreurs complète :
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 :
- Journalisent l’exception complète avec la stack trace
- Enregistrent une métrique d’échec
- Renvoient une réponse 500 sécurisée au client
- Préservent l’ID de corrélation
Il est recommandé de spécifier des modèles de réponse pour vos opérations API afin d’améliorer la génération de code si vous utilisez le générateur api-connection
. Voir ici pour plus de détails.
Streaming
Section intitulée « Streaming »Avec FastAPI, vous pouvez streamer une réponse au client avec le type de réponse StreamingResponse
.
Modifications d’infrastructure
Section intitulée « Modifications d’infrastructure »Puisqu’AWS API Gateway ne supporte pas les réponses streamées, vous devrez déployer votre FastAPI sur une plateforme qui le supporte. L’option la plus simple est d’utiliser une URL de fonction Lambda AWS.
Pour cela, vous pouvez remplacer le construct généré common/constructs/src/app/apis/<name>-api.ts
par un qui déploie une URL de fonction.
Exemple de construct FunctionURL pour le streaming
import { Duration, Stack, CfnOutput } from 'aws-cdk-lib';import { IGrantable, Grant } from 'aws-cdk-lib/aws-iam';import { Runtime, Code, Tracing, LayerVersion, FunctionUrlAuthType, InvokeMode, Function,} from 'aws-cdk-lib/aws-lambda';import { Construct } from 'constructs';import url from 'url';import { RuntimeConfig } from '../../core/runtime-config.js';
export class MyApi extends Construct { public readonly handler: Function;
constructor(scope: Construct, id: string) { super(scope, id);
this.handler = new Function(this, 'Handler', { runtime: Runtime.PYTHON_3_12, handler: 'run.sh', code: Code.fromAsset( url.fileURLToPath( new URL( '../../../../../../dist/packages/my_api/bundle', import.meta.url, ), ), ), timeout: Duration.seconds(30), tracing: Tracing.ACTIVE, environment: { AWS_CONNECTION_REUSE_ENABLED: '1', }, });
const stack = Stack.of(this); this.handler.addLayers( LayerVersion.fromLayerVersionArn( this, 'LWALayer', `arn:aws:lambda:${stack.region}:753240598075:layer:LambdaAdapterLayerX86:24`, ), ); this.handler.addEnvironment('PORT', '8000'); this.handler.addEnvironment('AWS_LWA_INVOKE_MODE', 'response_stream'); this.handler.addEnvironment('AWS_LAMBDA_EXEC_WRAPPER', '/opt/bootstrap'); const functionUrl = this.handler.addFunctionUrl({ authType: FunctionUrlAuthType.AWS_IAM, invokeMode: InvokeMode.RESPONSE_STREAM, cors: { allowedOrigins: ['*'], allowedHeaders: [ 'authorization', 'content-type', 'x-amz-content-sha256', 'x-amz-date', 'x-amz-security-token', ], }, });
new CfnOutput(this, 'MyApiUrl', { value: functionUrl.url });
// Enregistre l'URL de l'API dans la configuration runtime pour la découverte client RuntimeConfig.ensure(this).config.apis = { ...RuntimeConfig.ensure(this).config.apis!, MyApi: functionUrl.url, }; }
public grantInvokeAccess(grantee: IGrantable) { Grant.addToPrincipal({ grantee, actions: ['lambda:InvokeFunctionUrl'], resourceArns: [this.handler.functionArn], conditions: { StringEquals: { 'lambda:FunctionUrlAuthType': 'AWS_IAM', }, }, }); }}
Pour cela avec Terraform, vous pouvez remplacer l’infrastructure API Gateway générée par une URL de fonction Lambda supportant le streaming de réponse.
Exemple de configuration Terraform pour une URL de fonction Lambda avec streaming
# Data sources pour le contexte AWS actueldata "aws_caller_identity" "current" {}data "aws_region" "current" {}
# Fonction Lambda pour FastAPI en streamingresource "aws_lambda_function" "my_api_handler" { filename = "../../../../../../dist/packages/my_api/bundle.zip" function_name = "my-api-handler" role = aws_iam_role.lambda_execution_role.arn handler = "run.sh" runtime = "python3.12" timeout = 30 source_code_hash = filebase64sha256("../../../../../../dist/packages/my_api/bundle.zip")
# Activation du tracing X-Ray tracing_config { mode = "Active" }
# Variables d'environnement pour Lambda Web Adapter environment { variables = { AWS_CONNECTION_REUSE_ENABLED = "1" PORT = "8000" AWS_LWA_INVOKE_MODE = "response_stream" AWS_LAMBDA_EXEC_WRAPPER = "/opt/bootstrap" } }
# Ajout de la couche Lambda Web Adapter layers = [ "arn:aws:lambda:${data.aws_region.current.name}:753240598075:layer:LambdaAdapterLayerX86:24" ]
depends_on = [ aws_iam_role_policy_attachment.lambda_logs, aws_cloudwatch_log_group.lambda_logs, ]}
# Groupe de logs CloudWatch pour la fonction Lambdaresource "aws_cloudwatch_log_group" "lambda_logs" { name = "/aws/lambda/my-api-handler" retention_in_days = 14}
# Rôle IAM pour l'exécution Lambdaresource "aws_iam_role" "lambda_execution_role" { name = "my-api-lambda-execution-role"
assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } } ] })}
# Attachement de la politique d'exécution de baseresource "aws_iam_role_policy_attachment" "lambda_logs" { role = aws_iam_role.lambda_execution_role.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"}
# Attachement de la politique de tracing X-Rayresource "aws_iam_role_policy_attachment" "lambda_xray" { role = aws_iam_role.lambda_execution_role.name policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"}
# URL de fonction Lambda avec support du streamingresource "aws_lambda_function_url" "my_api_url" { function_name = aws_lambda_function.my_api_handler.function_name authorization_type = "AWS_IAM" invoke_mode = "RESPONSE_STREAM"
cors { allow_credentials = false allow_origins = ["*"] allow_methods = ["*"] allow_headers = [ "authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token" ] expose_headers = ["date", "keep-alive"] max_age = 86400 }}
# Sortie de l'URL de fonctionoutput "my_api_url" { description = "URL pour la fonction Lambda FastAPI en streaming" value = aws_lambda_function_url.my_api_url.function_url}
# Optionnel : Création d'un paramètre SSM pour la configuration runtimeresource "aws_ssm_parameter" "my_api_url" { name = "/runtime-config/apis/MyApi" type = "String" value = aws_lambda_function_url.my_api_url.function_url
tags = { Environment = "production" Service = "my-api" }}
# Politique IAM pour autoriser l'accès d'invocation à l'URL de fonctionresource "aws_iam_policy" "my_api_invoke_policy" { name = "my-api-invoke-policy" description = "Politique pour autoriser l'invocation de l'URL de fonction Lambda FastAPI en streaming"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "lambda:InvokeFunctionUrl" Resource = aws_lambda_function.my_api_handler.arn Condition = { StringEquals = { "lambda:FunctionUrlAuthType" = "AWS_IAM" } } } ] })}
# Exemple : Attachement de la politique à un rôle (décommenter et modifier si nécessaire)# resource "aws_iam_role_policy_attachment" "my_api_invoke_access" {# role = var.authenticated_role_name# policy_arn = aws_iam_policy.my_api_invoke_policy.arn# }
Implémentation
Section intitulée « Implémentation »Une fois l’infrastructure mise à jour pour supporter le streaming, vous pouvez implémenter une API streaming dans FastAPI. L’API doit :
- Retourner une
StreamingResponse
- Déclarer le type de retour de chaque chunk de réponse
- Ajouter l’extension OpenAPI
x-streaming: true
si vous prévoyez d’utiliser API Connection.
Par exemple, si vous souhaitez streamer une série d’objets JSON depuis votre API, vous pouvez l’implémenter ainsi :
from pydantic import BaseModelfrom fastapi.responses import StreamingResponse
class Chunk(BaseModel): message: str timestamp: datetime
async def stream_chunks(): for i in range(0, 100): yield Chunk(message=f"This is chunk {i}", timestamp=datetime.now())
@app.get("/stream", openapi_extra={'x-streaming': True})def my_stream() -> Chunk: return StreamingResponse(stream_chunks(), media_type="application/json")
Consommation
Section intitulée « Consommation »Pour consommer un flux de réponses, vous pouvez utiliser le Générateur API Connection qui fournira une méthode typée pour itérer sur les chunks streamés.
Déploiement de votre API FastAPI
Section intitulée « Déploiement de votre API FastAPI »Le générateur FastAPI crée une infrastructure CDK ou Terraform en fonction du iacProvider
sélectionné. Vous pouvez l’utiliser pour déployer votre API.
Le construct CDK pour déployer votre API se trouve 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) { // Ajoutez l'API à votre stack const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}
Ceci configure :
- Une fonction AWS Lambda pour chaque opération de l’application FastAPI
- Une API Gateway HTTP/REST comme déclencheur
- Les rôles et permissions IAM
- Un groupe de logs CloudWatch
- La configuration de tracing X-Ray
- Un espace de noms pour les 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 :
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Variables d'environnement pour la fonction Lambda env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Politiques IAM supplémentaires si besoin additional_iam_policy_statements = [ # Ajoutez les permissions supplémentaires nécessaires ]
tags = local.common_tags}
Ceci configure :
- Une fonction AWS Lambda qui sert toutes les routes FastAPI
- Une API Gateway HTTP/REST comme déclencheur
- Les rôles et permissions IAM
- Un groupe de logs CloudWatch
- La configuration de tracing X-Ray
- La configuration CORS
Le module Terraform fournit plusieurs sorties utilisables :
# Accéder à l'endpoint de l'APIoutput "api_url" { value = module.my_api.stage_invoke_url}
# Accéder aux détails de la fonction Lambdaoutput "lambda_function_name" { value = module.my_api.lambda_function_name}
# Accéder au rôle IAM pour accorder des permissions supplémentairesoutput "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"
# Configuration CORS personnalisée 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}
Intégrations
Section intitulée « Intégrations »Les constructs CDK pour l’API REST/HTTP sont configurés pour fournir une interface typée permettant de définir des intégrations pour chacune de vos opérations.
Les constructs CDK offrent un support complet d’intégration typée 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 router avec une seule fonction Lambda. Aucune configuration supplémentaire n’est nécessaire :
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Le module crée automatiquement une fonction Lambda unique # qui gère toutes les opérations de l'API 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 typée. 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 est typé selon les opérations définies dans votre APIapi.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));
Avec le modèle router de Terraform, il n’y a qu’une seule fonction Lambda. Vous pouvez y accéder via les sorties du module :
# Accorder des permissions supplémentaires à la fonction Lambda uniqueresource "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 des fonctions Lambda pour chaque intégration par défaut, vous pouvez utiliser la méthode withDefaultOptions
. Par exemple, si vous voulez 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 :
# Ajouter des variables VPCvariable "vpc_subnet_ids" { description = "Liste des IDs de sous-réseaux VPC pour la fonction Lambda" type = list(string) default = []}
variable "vpc_security_group_ids" { description = "Liste des IDs de groupes de sécurité VPC pour la fonction Lambda" type = list(string) default = []}
# Mettre à jour la ressource Lambdaresource "aws_lambda_function" "api_lambda" { # ... configuration existante ...
# Ajouter la configuration VPC vpc_config { subnet_ids = var.vpc_subnet_ids security_group_ids = var.vpc_security_group_ids }}
Puis utiliser le module avec la configuration VPC :
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Configuration VPC vpc_subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id] vpc_security_group_ids = [aws_security_group.lambda_sg.id]
tags = local.common_tags}
Surcharge des intégrations
Section intitulée « Surcharge des intégrations »Vous pouvez aussi surcharger les intégrations pour des opérations spécifiques en utilisant la méthode withOverrides
. Chaque surcharge doit spécifier une propriété integration
typée selon le construct CDK d’intégration approprié pour l’API HTTP ou REST. La méthode withOverrides
est également typée. Par exemple, si vous voulez rediriger une API getDocumentation
vers une documentation hébergée sur un site externe :
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), }, }) .build(),});
Vous remarquerez que l’intégration surchargée n’a plus de propriété handler
lorsqu’on y accède via api.integrations.getDocumentation
.
Vous pouvez ajouter des propriétés supplémentaires à une intégration qui seront également typées, permettant d’abstraire d’autres types d’intégration tout en conservant le typage. Par exemple, si vous avez créé une intégration S3 pour une API REST et que vous souhaitez référencer le bucket pour une opération particulière :
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(),});
// Plus tard, peut-être dans un autre fichier, vous pouvez accéder à la propriété bucket// que nous avons définie de manière typéeapi.integrations.getFile.bucket.grantRead(...);
Surcharge des autorisations
Section intitulée « Surcharge des autorisations »Vous pouvez aussi fournir des options
dans votre intégration pour surcharger des options de méthode spécifiques comme les autorisations. 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(...) // pour REST, ou HttpUserPoolAuthorizer pour une API HTTP } }, }) .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 fournir directement une intégration pour chaque opération. Ceci est utile si, par exemple, chaque opération nécessite un type d’intégration différent ou si vous voulez obtenir 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 généré spécifique à l’application pour remplacer l’intégration proxy par défaut par des intégrations spécifiques à chaque opération.
Modifiez packages/common/terraform/src/app/apis/my-api/my-api.tf
:
- Supprimer les routes proxy par défaut (ex:
resource "aws_apigatewayv2_route" "proxy_routes"
) - Remplacer la fonction Lambda unique par des fonctions individuelles pour chaque opération
- Créer des intégrations et routes spécifiques pour chaque opération, en réutilisant le même bundle ZIP :
# Supprimer la fonction Lambda unique par défaut 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 # ... reste de la configuration }
# Supprimer l'intégration proxy par défaut 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 # ... reste de la configuration }
# Supprimer les routes proxy par défaut 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}" # ... reste de la configuration }
# Ajouter des fonctions Lambda individuelles pour chaque opération en utilisant le même 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" # Handler spécifique pour cette opération runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Handler spécifique pour cette opération runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
# Ajouter des intégrations spécifiques pour chaque opération 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" }
# Ajouter des routes spécifiques pour chaque opération 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" }
# Ajouter des permissions Lambda pour chaque fonction 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}/*/*" }
# Supprimer la fonction Lambda unique par défaut 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 # ... reste de la configuration }
# Supprimer l'intégration proxy par défaut 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 # ... reste de la configuration }
# Supprimer les routes proxy par défaut 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}" # ... reste de la configuration }
# Ajouter des fonctions Lambda individuelles pour chaque opération en utilisant le même 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" # Handler spécifique pour cette opération runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # Handler spécifique pour cette opération runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
# Ajouter des ressources et méthodes spécifiques pour chaque opération 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" }
# Mettre à jour le déploiement pour dépendre des nouvelles intégrations~ 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, ])) } }
# Ajouter des permissions Lambda pour chaque fonction 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 Router
Section intitulée « Modèle Router »Si vous préférez déployer une seule fonction Lambda pour gérer toutes les requêtes API, vous pouvez librement modifier la méthode defaultIntegrations
de votre API pour créer une seule fonction au lieu d’une par intégration :
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { const router = new Function(scope, 'RouterHandler', { ... }); return IntegrationBuilder.rest({ ... defaultIntegrationOptions: {}, buildDefaultIntegration: (op) => { return { # Référencer le même handler router dans chaque intégration integration: new LambdaIntegration(router), }; }, }); };}
Vous pouvez modifier le code selon vos préférences, par exemple définir la fonction router
comme paramètre de defaultIntegrations
au lieu de la construire dans la méthode.
Les modules Terraform utilisent automatiquement le modèle router - c’est l’approche par défaut et la seule supportée. Le module généré crée une fonction Lambda unique qui gère toutes les opérations API.
Vous pouvez simplement instancier le module par défaut pour obtenir le modèle router :
# Modèle router par défaut - fonction Lambda unique pour toutes les opérationsmodule "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# La fonction Lambda unique gère automatiquement toutes les opérations tags = local.common_tags}
Génération de code
Section intitulée « Génération de code »Comme les opérations 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 d’offrir une interface typée pour les intégrations.
Une cible generate:<ApiName>-metadata
est ajoutée au project.json
des constructs communs pour faciliter cette génération, produisant un fichier comme packages/common/constructs/src/generated/my-api/metadata.gen.ts
. Ce fichier étant généré au build, il est ignoré dans le contrôle de version.
Si vous travaillez simultanément sur l’infrastructure CDK et FastAPI, vous pouvez utiliser nx watch
pour régénérer ces types à chaque modification :
pnpm nx watch --projects=<FastAPIProject> -- \pnpm nx run <InfraProject>:"generate:<ApiName>-metadata"
yarn nx watch --projects=<FastAPIProject> -- \yarn nx run <InfraProject>:"generate:<ApiName>-metadata"
npx nx watch --projects=<FastAPIProject> -- \npx nx run <InfraProject>:"generate:<ApiName>-metadata"
bunx nx watch --projects=<FastAPIProject> -- \bunx nx run <InfraProject>:"generate:<ApiName>-metadata"
Octroi d’accès (IAM uniquement)
Section intitulée « Octroi d’accès (IAM uniquement) »Si vous avez choisi l’authentification IAM
, vous pouvez utiliser la méthode grantInvokeAccess
pour octroyer l’accès à votre API :
api.grantInvokeAccess(myIdentityPool.authenticatedRole);
# Création d'une politique IAM pour autoriser l'invocation de l'APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Politique pour autoriser l'invocation de l'API FastAPI"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Attachement de la politique à un rôle IAM (ex: utilisateurs authentifiés)resource "aws_iam_role_policy_attachment" "api_invoke_access" { role = aws_iam_role.authenticated_user_role.name policy_arn = aws_iam_policy.api_invoke_policy.arn}
# Ou attachement à un rôle existant par nomresource "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 utilisables pour les politiques IAM sont :
module.my_api.api_execution_arn
- Pour autoriser execute-api:Invokemodule.my_api.api_arn
- L’ARN de l’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 exécutable avec :
pnpm nx run my-api:serve
yarn nx run my-api:serve
npx nx run my-api:serve
bunx nx run my-api:serve
Ceci démarre un serveur de développement FastAPI avec :
- Rechargement automatique sur modification
- Documentation interactive de l’API sur
/docs
ou/redoc
- Schéma OpenAPI sur
/openapi.json
Invocation de votre API FastAPI
Section intitulée « Invocation de votre API FastAPI »Pour invoquer votre API depuis un site React, vous pouvez utiliser le générateur api-connection
.