Documentation de l'API Smithy TypeScript
Smithy est un langage de définition d’interface indépendant des protocoles pour créer des API de manière modélisée.
Le générateur d’API Smithy TypeScript crée une nouvelle API en utilisant Smithy pour la définition des services, et le SDK serveur Smithy TypeScript pour l’implémentation. Le générateur fournit une infrastructure IaC avec CDK ou Terraform pour déployer votre service sur AWS Lambda, exposé via une API REST AWS API Gateway. Il permet un développement d’API fortement typé avec génération automatique de code à partir des modèles Smithy. Le gestionnaire généré utilise AWS Lambda Powertools pour TypeScript 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 API Smithy TypeScript
Section intitulée « Générer une API Smithy TypeScript »Vous pouvez générer une nouvelle API Smithy TypeScript 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 - ts#smithy-api
- Remplissez les paramètres requis
- Cliquez sur
Generate
pnpm nx g @aws/nx-plugin:ts#smithy-api
yarn nx g @aws/nx-plugin:ts#smithy-api
npx nx g @aws/nx-plugin:ts#smithy-api
bunx nx g @aws/nx-plugin:ts#smithy-api
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
pnpm nx g @aws/nx-plugin:ts#smithy-api --dry-run
yarn nx g @aws/nx-plugin:ts#smithy-api --dry-run
npx nx g @aws/nx-plugin:ts#smithy-api --dry-run
bunx nx g @aws/nx-plugin:ts#smithy-api --dry-run
Paramètre | Type | Par défaut | Description |
---|---|---|---|
name Requis | string | - | The name of the API (required). Used to generate class names and file paths. |
namespace | string | - | The namespace for the Smithy API. Defaults to your monorepo scope |
computeType | string | ServerlessApiGatewayRestApi | The type of compute to use to deploy this API. |
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 | Inherit | The preferred IaC provider. By default this is inherited from your initial selection. |
Sortie du générateur
Section intitulée « Sortie du générateur »Le générateur crée deux projets associés dans le répertoire <directory>/<api-name>
:
Répertoiremodel/ Projet de modèle Smithy
- project.json Configuration du projet et cibles de build
- smithy-build.json Configuration de build Smithy
- build.Dockerfile Configuration Docker pour la construction des artefacts Smithy
Répertoiresrc/
- main.smithy Définition principale du service
Répertoireoperations/
- echo.smithy Exemple de définition d’opération
Répertoirebackend/ Implémentation TypeScript du backend
- project.json Configuration du projet et cibles de build
- rolldown.config.ts Configuration de bundle
Répertoiresrc/
- handler.ts Gestionnaire AWS Lambda
- local-server.ts Serveur de développement local
- service.ts Implémentation du service
- context.ts Définition du contexte du service
Répertoireoperations/
- echo.ts Exemple d’implémentation d’opération
Répertoiregenerated/ SDK TypeScript généré (créé pendant le build)
- …
Infrastructure
Section intitulée « Infrastructure »Comme ce générateur crée une infrastructure as code selon votre choix de iacProvider
, il génère un projet dans packages/common
incluant les constructs CDK ou modules Terraform pertinents.
Le projet d’infrastructure as 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épertoireapis/
- <project-name>.ts Construct CDK pour déployer votre API
Répertoirecore/ Constructs génériques réutilisés par ceux dans
app
Répertoireapi/
- rest-api.ts Construct CDK pour déployer une API REST
- utils.ts Utilitaires pour les constructs d’API
- index.ts Point d’entrée exportant les constructs de
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épertoireapis/
Répertoire<project-name>/
- <project-name>.tf Module pour déployer votre API
Répertoirecore/ Modules génériques réutilisés par ceux dans
app
Répertoireapi/
Répertoirerest-api/
- rest-api.tf Module pour déployer une API REST
- project.json Cibles de build et configuration du projet
Implémentation de votre API Smithy
Section intitulée « Implémentation de votre API Smithy »Définition d’opérations dans Smithy
Section intitulée « Définition d’opérations dans Smithy »Les opérations sont définies dans des fichiers Smithy au sein du projet de modèle. La définition principale du service se trouve dans main.smithy
:
$version: "2.0"
namespace your.namespace
use aws.protocols#restJson1use smithy.framework#ValidationException
@title("YourService")@restJson1service YourService { version: "1.0.0" operations: [ Echo, // Ajoutez vos opérations ici ] errors: [ ValidationException ]}
Les opérations individuelles sont définies dans des fichiers séparés dans le répertoire operations/
:
$version: "2.0"
namespace your.namespace
@http(method: "POST", uri: "/echo")operation Echo { input: EchoInput output: EchoOutput}
structure EchoInput { @required message: String
foo: Integer bar: String}
structure EchoOutput { @required message: String}
Implémentation des opérations en TypeScript
Section intitulée « Implémentation des opérations en TypeScript »Les implémentations d’opérations se trouvent dans le répertoire src/operations/
du projet backend. Chaque opération est implémentée en utilisant les types générés par le SDK serveur TypeScript (généré au moment du build à partir de votre modèle Smithy).
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input) => { // Votre logique métier ici return { message: `Echo: ${input.message}` // fortement typé selon votre modèle Smithy };};
Les opérations doivent être enregistrées dans la définition du service dans src/service.ts
:
import { ServiceContext } from './context.js';import { YourServiceService } from './generated/ssdk/index.js';import { Echo } from './operations/echo.js';// Importez d'autres opérations ici
// Enregistrez les opérations dans le service iciexport const Service: YourServiceService<ServiceContext> = { Echo, // Ajoutez d'autres opérations ici};
Contexte du service
Section intitulée « Contexte du service »Vous pouvez définir un contexte partagé pour vos opérations dans context.ts
:
export interface ServiceContext { // Tracer, logger et metrics Powertools sont fournis par défaut tracer: Tracer; logger: Logger; metrics: Metrics; // Ajoutez des dépendances partagées, connexions DB, etc. dbClient: any; userIdentity: string;}
Ce contexte est passé à toutes les implémentations d’opérations et peut être utilisé pour partager des ressources comme des connexions de base de données, de la configuration ou des utilitaires de journalisation.
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 et l’injection automatique de contexte via un middleware Middy.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
Vous pouvez accéder au logger depuis vos implémentations d’opérations via le contexte :
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { ctx.logger.info('Votre message de log'); // ...};
Le traçage AWS X-Ray est configuré automatiquement via le middleware captureLambdaHandler
.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
Vous pouvez ajouter des sous-segments personnalisés à vos traces dans vos opérations :
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { // Crée un nouveau sous-segment const subsegment = ctx.tracer.getSegment()?.addNewSubsegment('custom-operation'); try { // Votre logique ici } catch (error) { subsegment?.addError(error as Error); throw error; } finally { subsegment?.close(); }};
Métriques
Section intitulée « Métriques »Les métriques CloudWatch sont collectées automatiquement pour chaque requête via le middleware logMetrics
.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
Vous pouvez ajouter des métriques personnalisées dans vos opérations :
import { MetricUnit } from '@aws-lambda-powertools/metrics';import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { ctx.metrics.addMetric("CustomMetric", MetricUnit.Count, 1); // ...};
Gestion des erreurs
Section intitulée « Gestion des erreurs »Smithy fournit une gestion d’erreurs intégrée. Vous pouvez définir des erreurs personnalisées dans votre modèle Smithy :
@error("client")@httpError(400)structure InvalidRequestError { @required message: String}
Et les enregistrer dans votre opération/service :
operation MyOperation { ... errors: [InvalidRequestError]}
Puis les lever dans votre implémentation TypeScript :
import { InvalidRequestError } from '../generated/ssdk/index.js';
export const MyOperation: MyOperationHandler<ServiceContext> = async (input) => { if (!input.requiredField) { throw new InvalidRequestError({ message: "Champ requis manquant" }); }
return { /* réponse de succès */ };};
Build et génération de code
Section intitulée « Build et génération de code »Le projet de modèle Smithy utilise Docker pour construire les artefacts Smithy et générer le SDK serveur TypeScript :
pnpm nx run <model-project>:build
yarn nx run <model-project>:build
npx nx run <model-project>:build
bunx nx run <model-project>:build
Ce processus :
- Compile le modèle Smithy et le valide
- Génère une spécification OpenAPI à partir du modèle Smithy
- Crée le SDK serveur TypeScript avec des interfaces d’opération fortement typées
- Produit les artefacts de build dans
dist/<model-project>/build/
Le projet backend copie automatiquement le SDK généré pendant la compilation :
pnpm nx run <backend-project>:copy-ssdk
yarn nx run <backend-project>:copy-ssdk
npx nx run <backend-project>:copy-ssdk
bunx nx run <backend-project>:copy-ssdk
Cible de bundle
Section intitulée « Cible de bundle »Le générateur configure automatiquement une cible bundle
qui utilise Rolldown pour créer un package de déploiement :
pnpm nx run <project-name>:bundle
yarn nx run <project-name>:bundle
npx nx run <project-name>:bundle
bunx nx run <project-name>:bundle
La configuration de Rolldown se trouve dans rolldown.config.ts
, avec une entrée par bundle à générer. Rolldown gère la création de plusieurs bundles en parallèle si définis.
Développement local
Section intitulée « Développement local »Le générateur configure un serveur de développement local avec rechargement à chaud :
pnpm nx run <backend-project>:serve
yarn nx run <backend-project>:serve
npx nx run <backend-project>:serve
bunx nx run <backend-project>:serve
Déploiement de votre API Smithy
Section intitulée « Déploiement de votre API Smithy »Le générateur crée une infrastructure CDK ou Terraform selon votre choix de iacProvider
.
Le construct CDK pour déployer votre API se trouve dans le dossier common/constructs
:
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 le service Smithy
- Une API REST API Gateway comme déclencheur
- Les rôles et permissions IAM
- Un groupe de logs CloudWatch
- La configuration de traçage X-Ray
Les modules Terraform pour déployer votre API se trouvent dans le dossier common/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 nécessaire additional_iam_policy_statements = [ # Ajoutez les permissions supplémentaires nécessaires ]
tags = local.common_tags}
Ceci configure :
- Une fonction AWS Lambda servant l’API Smithy
- Une API REST API Gateway comme déclencheur
- Les rôles et permissions IAM
- Un groupe de logs CloudWatch
- La configuration de traçage X-Ray
- La configuration CORS
Le module Terraform fournit plusieurs sorties :
# Accéder à l'URL 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}
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 sont définies dans Smithy, nous utilisons la génération de code pour fournir des métadonnées au construct CDK pour des intégrations fortement typées.
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.
Octroi d’accès (IAM uniquement)
Section intitulée « Octroi d’accès (IAM uniquement) »Si vous avez sélectionné l’authentification IAM
, vous pouvez utiliser la méthode grantInvokeAccess
pour accorder l’accès à votre API :
api.grantInvokeAccess(myIdentityPool.authenticatedRole);
# Créer une politique IAM pour autoriser l'appel de l'APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Politique permettant d'invoquer l'API Smithy"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Attacher la politique à un rôle IAMresource "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}
Invocation de votre API Smithy
Section intitulée « Invocation de votre API Smithy »Pour invoquer votre API depuis un site React, vous pouvez utiliser le générateur api-connection
, qui fournit un client fortement typé généré à partir de votre modèle Smithy.