Smithy TypeScript API
Smithy is a protocol-agnostic interface definition language for authoring APIs in a model driven fashion.
The Smithy TypeScript API generator creates a new API using Smithy for service definition, and the Smithy TypeScript Server SDK for implementation. The generator vends CDK or Terraform infrastructure as code to deploy your service to AWS Lambda, exposed via an AWS API Gateway REST API. It provides type-safe API development with automatic code generation from Smithy models. The generated handler uses AWS Lambda Powertools for TypeScript for observability, including logging, AWS X-Ray tracing and CloudWatch Metrics
Generate a Smithy TypeScript API
Section titled “Generate a Smithy TypeScript API”You can generate a new Smithy TypeScript API in two ways:
- Install the Nx Console VSCode Plugin if you haven't already
- Open the Nx Console in VSCode
- Click
Generate (UI)
in the "Common Nx Commands" section - Search for
@aws/nx-plugin - ts#smithy-api
- Fill in the required parameters
- Click
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
You can also perform a dry-run to see what files would be changed
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
Options
Section titled “Options”Parameter | Type | Default | Description |
---|---|---|---|
name Required | 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. |
Generator Output
Section titled “Generator Output”The generator creates two related projects in the <directory>/<api-name>
directory:
Directorymodel/ Smithy model project
- project.json Project configuration and build targets
- smithy-build.json Smithy build configuration
- build.Dockerfile Docker configuration for building Smithy artifacts
Directorysrc/
- main.smithy Main service definition
Directoryoperations/
- echo.smithy Example operation definition
Directorybackend/ TypeScript backend implementation
- project.json Project configuration and build targets
- rolldown.config.ts Bundle configuration
Directorysrc/
- handler.ts AWS Lambda handler
- local-server.ts Local development server
- service.ts Service implementation
- context.ts Service context definition
Directoryoperations/
- echo.ts Example operation implementation
Directorygenerated/ Generated TypeScript SDK (created during build)
- …
Infrastructure
Section titled “Infrastructure”Since this generator creates infrastructure as code based on your chosen iacProvider
, it will create a project in packages/common
which includes the relevant CDK constructs or Terraform modules.
The common infrastructure as code project is structured as follows:
Directorypackages/common/constructs
Directorysrc
Directoryapp/ Constructs for infrastructure specific to a project/generator
Directoryapis/
- <project-name>.ts CDK construct for deploying your API
Directorycore/ Generic constructs which are reused by constructs in
app
Directoryapi/
- rest-api.ts CDK construct for deploying a REST API
- utils.ts Utilities for the API constructs
- index.ts Entry point exporting constructs from
app
- project.json Project build targets and configuration
Directorypackages/common/terraform
Directorysrc
Directoryapp/ Terraform modules for infrastructure specific to a project/generator
Directoryapis/
Directory<project-name>/
- <project-name>.tf Module for deploying your API
Directorycore/ Generic modules which are reused by modules in
app
Directoryapi/
Directoryrest-api/
- rest-api.tf Module for deploying a REST API
- project.json Project build targets and configuration
Implementing your Smithy API
Section titled “Implementing your Smithy API”Defining Operations in Smithy
Section titled “Defining Operations in Smithy”Operations are defined in Smithy files within the model project. The main service definition is in 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, // Add your operations here ] errors: [ ValidationException ]}
Individual operations are defined in separate files in the operations/
directory:
$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}
Implementing Operations in TypeScript
Section titled “Implementing Operations in TypeScript”Operation implementations are located in the backend project’s src/operations/
directory. Each operation is implemented using the generated types from the TypeScript Server SDK (generated at build time from your Smithy model).
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input) => { // Your business logic here return { message: `Echo: ${input.message}` // type-safe based on your Smithy model };};
Operations must be registered to the service definition in src/service.ts
:
import { ServiceContext } from './context.js';import { YourServiceService } from './generated/ssdk/index.js';import { Echo } from './operations/echo.js';// Import other operations here
// Register operations to the service hereexport const Service: YourServiceService<ServiceContext> = { Echo, // Add other operations here};
Service Context
Section titled “Service Context”You can define shared context for your operations in context.ts
:
export interface ServiceContext { // Powertools tracer, logger and metrics are provided by default tracer: Tracer; logger: Logger; metrics: Metrics; // Add shared dependencies, database connections, etc. dbClient: any; userIdentity: string;}
This context is passed to all operation implementations and can be used to share resources like database connections, configuration, or logging utilities.
Observability with AWS Lambda Powertools
Section titled “Observability with AWS Lambda Powertools”Logging
Section titled “Logging”The generator configures structured logging using AWS Lambda Powertools with automatic context injection via Middy middleware.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
You can reference the logger from your operation implementations via the context:
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('Your log message'); // ...};
Tracing
Section titled “Tracing”AWS X-Ray tracing is configured automatically via the captureLambdaHandler
middleware.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
You can add custom subsegments to your traces in your operations:
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { // Creates a new subsegment const subsegment = ctx.tracer.getSegment()?.addNewSubsegment('custom-operation'); try { // Your logic here } catch (error) { subsegment?.addError(error as Error); throw error; } finally { subsegment?.close(); }};
Metrics
Section titled “Metrics”CloudWatch metrics are collected automatically for each request via the logMetrics
middleware.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);
You can add custom metrics in your operations:
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); // ...};
Error Handling
Section titled “Error Handling”Smithy provides built-in error handling. You can define custom errors in your Smithy model:
@error("client")@httpError(400)structure InvalidRequestError { @required message: String}
And register them to your operation/service:
operation MyOperation { ... errors: [InvalidRequestError]}
Then throw them in your TypeScript implementation:
import { InvalidRequestError } from '../generated/ssdk/index.js';
export const MyOperation: MyOperationHandler<ServiceContext> = async (input) => { if (!input.requiredField) { throw new InvalidRequestError({ message: "Required field is missing" }); }
return { /* success response */ };};
Building and Code Generation
Section titled “Building and Code Generation”The Smithy model project uses Docker to build the Smithy artifacts and generate the TypeScript Server SDK:
pnpm nx run <model-project>:build
yarn nx run <model-project>:build
npx nx run <model-project>:build
bunx nx run <model-project>:build
This process:
- Compiles the Smithy model and validates it
- Generates OpenAPI specification from the Smithy model
- Creates TypeScript Server SDK with type-safe operation interfaces
- Outputs build artifacts to
dist/<model-project>/build/
The backend project automatically copies the generated SDK during 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
Bundle Target
Section titled “Bundle Target”The generator automatically configures a bundle
target which uses Rolldown to create a deployment package:
pnpm nx run <project-name>:bundle
yarn nx run <project-name>:bundle
npx nx run <project-name>:bundle
bunx nx run <project-name>:bundle
Rolldown configuration can be found in rolldown.config.ts
, with an entry per bundle to generate. Rolldown manages creating multiple bundles in parallel if defined.
Local Development
Section titled “Local Development”The generator configures a local development server with hot reloading:
pnpm nx run <backend-project>:serve
yarn nx run <backend-project>:serve
npx nx run <backend-project>:serve
bunx nx run <backend-project>:serve
Deploying your Smithy API
Section titled “Deploying your Smithy API”The generator creates CDK or Terraform infrastructure based on your selected iacProvider
.
The CDK construct for deploying your API is in the common/constructs
folder:
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(), }); }}
This sets up:
- An AWS Lambda function for the Smithy service
- API Gateway REST API as the function trigger
- IAM roles and permissions
- CloudWatch log group
- X-Ray tracing configuration
The Terraform modules for deploying your API are in the common/terraform
folder:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# 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}
This sets up:
- An AWS Lambda function that serves the Smithy API
- API Gateway REST API as the function trigger
- IAM roles and permissions
- CloudWatch log group
- X-Ray tracing configuration
- CORS configuration
The Terraform module provides several outputs:
# 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}
Integrations
Section titled “Integrations”The REST/HTTP API CDK constructs are configured to provide a type-safe interface for defining integrations for each of your operations.
Default Integrations
Section titled “Default Integrations”You can use the static defaultIntegrations
to make use of the default pattern, which defines an individual AWS Lambda function for each operation:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
Terraform modules automatically use the router pattern with a single Lambda function. No additional configuration is needed:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# The module automatically creates a single Lambda function # that handles all API operations tags = local.common_tags}
Accessing Integrations
Section titled “Accessing Integrations”You can access the underlying AWS Lambda functions via the API construct’s integrations
property, in a type-safe manner. For example, if your API defines an operation named sayHello
and you need to add some permissions to this function, you can do so as follows:
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: [...],}));
With Terraform’s router pattern, there’s only one Lambda function. You can access it via the module outputs:
# Grant additional permissions to the single Lambda functionresource "aws_iam_role_policy" "additional_permissions" { name = "additional-api-permissions" role = module.my_api.lambda_execution_role_name
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject" ] Resource = "arn:aws:s3:::my-bucket/*" } ] })}
Customising Default Options
Section titled “Customising Default Options”If you would like to customise the options used when creating the Lambda function for each default integration, you can use the withDefaultOptions
method. For example, if you would like all of your Lambda functions to reside in a Vpc:
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});
To customize options like VPC configuration, you need to edit the generated Terraform module. For example, to add VPC support to all Lambda functions:
# Add VPC variablesvariable "vpc_subnet_ids" { description = "List of VPC subnet IDs for Lambda function" type = list(string) default = []}
variable "vpc_security_group_ids" { description = "List of VPC security group IDs for Lambda function" type = list(string) default = []}
# Update the Lambda function resourceresource "aws_lambda_function" "api_lambda" { # ... existing configuration ...
# Add VPC configuration vpc_config { subnet_ids = var.vpc_subnet_ids security_group_ids = var.vpc_security_group_ids }}
Then use the module with VPC configuration:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# VPC configuration vpc_subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id] vpc_security_group_ids = [aws_security_group.lambda_sg.id]
tags = local.common_tags}
Overriding Integrations
Section titled “Overriding Integrations”You can also override integrations for specific operations using the withOverrides
method. Each override must specify an integration
property which is typed to the appropriate CDK integration construct for the HTTP or REST API. The withOverrides
method is also type-safe. For example, if you would like to override a getDocumentation
API to point to documentation hosted by some external website you could achieve this as follows:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), }, }) .build(),});
You will also notice that the overridden integration no longer has a handler
property when accessing it via api.integrations.getDocumentation
.
You can add additional properties to an integration which will also be typed accordingly, allowing for other types of integration to be abstracted but remain type-safe, for example if you have created an S3 integration for a REST API and later wish to reference the bucket for a particular operation, you can do so as follows:
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(...);
Overriding Authorizers
Section titled “Overriding Authorizers”You can also supply options
in your integration to override particular method options such as authorizers, for example if you wished to use Cognito authentication for your getDocumentation
operation:
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(),});
Explicit Integrations
Section titled “Explicit Integrations”If you prefer, you can choose not to use the default integrations and instead directly supply one for each operation. This is useful if, for example, each operation needs to use a different type of integration or you would like to receive a type error when adding new operations:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});
For explicit per-operation integrations with Terraform, you should modify the generated app-specific module to replace the default proxy integration with specific integrations for each operation.
Edit packages/common/terraform/src/app/apis/my-api/my-api.tf
:
- Remove the default proxy routes (e.g.,
resource "aws_apigatewayv2_route" "proxy_routes"
) - Replace the single Lambda function with individual functions for each operation
- Create specific integrations and routes for each operation, reusing the same ZIP bundle:
# Remove the default single Lambda function resource "aws_lambda_function" "api_lambda" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... rest of configuration }
# Remove the default proxy integration resource "aws_apigatewayv2_integration" "lambda_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.api_lambda.invoke_arn # ... rest of configuration }
# Remove the default proxy routes resource "aws_apigatewayv2_route" "proxy_routes" { for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]) api_id = module.http_api.api_id route_key = "${each.key} /{proxy+}" target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" # ... rest of configuration }
# Add individual Lambda functions for each operation using the same bundle resource "aws_lambda_function" "say_hello_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = 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" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
# Add specific integrations for each operation resource "aws_apigatewayv2_integration" "say_hello_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.say_hello_handler.invoke_arn payload_format_version = "2.0" timeout_milliseconds = 30000 }
resource "aws_apigatewayv2_integration" "get_documentation_integration" { api_id = module.http_api.api_id integration_type = "HTTP_PROXY" integration_uri = "https://example.com/documentation" integration_method = "GET" }
# Add specific routes for each operation resource "aws_apigatewayv2_route" "say_hello_route" { api_id = module.http_api.api_id route_key = "POST /sayHello" target = "integrations/${aws_apigatewayv2_integration.say_hello_integration.id}" authorization_type = "AWS_IAM" }
resource "aws_apigatewayv2_route" "get_documentation_route" { api_id = module.http_api.api_id route_key = "GET /documentation" target = "integrations/${aws_apigatewayv2_integration.get_documentation_integration.id}" authorization_type = "NONE" }
# Add Lambda permissions for each function resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }
# Remove the default single Lambda function resource "aws_lambda_function" "api_lambda" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... rest of configuration }
# Remove the default proxy integration resource "aws_apigatewayv2_integration" "lambda_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.api_lambda.invoke_arn # ... rest of configuration }
# Remove the default proxy routes resource "aws_apigatewayv2_route" "proxy_routes" { for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]) api_id = module.http_api.api_id route_key = "${each.key} /{proxy+}" target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" # ... rest of configuration }
# Add individual Lambda functions for each operation using the same bundle resource "aws_lambda_function" "say_hello_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = 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" # Specific handler for this operation runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
# Add specific resources and methods for each operation resource "aws_api_gateway_resource" "say_hello_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "sayHello" }
resource "aws_api_gateway_method" "say_hello_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = "POST" authorization = "AWS_IAM" }
resource "aws_api_gateway_integration" "say_hello_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = aws_api_gateway_method.say_hello_method.http_method
integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.say_hello_handler.invoke_arn }
resource "aws_api_gateway_resource" "get_documentation_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "documentation" }
resource "aws_api_gateway_method" "get_documentation_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = "GET" authorization = "NONE" }
resource "aws_api_gateway_integration" "get_documentation_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = aws_api_gateway_method.get_documentation_method.http_method
integration_http_method = "GET" type = "HTTP" uri = "https://example.com/documentation" }
# Update deployment to depend on new integrations~ resource "aws_api_gateway_deployment" "api_deployment" { rest_api_id = module.rest_api.api_id
depends_on = [ aws_api_gateway_integration.lambda_integration, aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ]
lifecycle { create_before_destroy = true }
triggers = { redeployment = sha1(jsonencode([ aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ])) } }
# Add Lambda permissions for each function resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }
Router Pattern
Section titled “Router Pattern”If you prefer to deploy a single Lambda function to service all API requests, you can freely edit the defaultIntegrations
method for your API to create a single function instead of one per integration:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { const router = new Function(scope, 'RouterHandler', { ... }); return IntegrationBuilder.rest({ ... defaultIntegrationOptions: {}, buildDefaultIntegration: (op) => { return { // Reference the same router lambda handler in every integration integration: new LambdaIntegration(router), }; }, }); };}
You can modify the code in other ways if you prefer, for example you may prefer to define the router
function as a parameter to defaultIntegrations
instead of constructing it within the method.
Terraform modules automatically use the router pattern - this is the default and only supported approach. The generated module creates a single Lambda function that handles all API operations.
You can simply instantiate the default module to get the router pattern:
# Default router pattern - single Lambda function for all operationsmodule "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Single Lambda function handles all operations automatically tags = local.common_tags}
Code Generation
Section titled “Code Generation”Since operations are defined in Smithy, we use code generation to supply metadata to the CDK construct for type-safe integrations.
A generate:<ApiName>-metadata
target is added to the common constructs project.json
to facilitate this code generation, which emits a file such as packages/common/constructs/src/generated/my-api/metadata.gen.ts
. Since this is generated at build time, it is ignored in version control.
Granting Access (IAM Only)
Section titled “Granting Access (IAM Only)”If you selected IAM
authentication, you can use the grantInvokeAccess
method to grant access to your 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 Smithy API"
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 roleresource "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}
Invoking your Smithy API
Section titled “Invoking your Smithy API”To invoke your API from a React website, you can use the api-connection
generator, which provides type-safe client generation from your Smithy model.