A reference CDK stack
Published: 2024-06-20 09:36:58

```typescript

import { Construct, IConstruct } from 'constructs';
import * as cdk from 'aws-cdk-lib/core';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';

export interface MyStackProps extends cdk.StackProps {
  stageName: string;
  prefix: string;
  retainData: boolean;
}

export class CdkApiSqsStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: MyStackProps) {
    super(scope, id, props);

    // Create an SQS queue
    const queue = new sqs.Queue(this, 'BooksQueue', {
      visibilityTimeout: cdk.Duration.seconds(300),
      queueName: `${props.prefix}-books-queue-${props.stageName}`,
    });

    // Create a DynamoDB table
    const carsTable = new dynamodb.Table(this, 'CarsTable', {
      tableName: `${props.prefix}-cars-${props.stageName}`,
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      partitionKey: { name: 'carId', type: dynamodb.AttributeType.STRING },
      removalPolicy: props.retainData ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, // NOT recommended for production code
    });

    // Create a REST API
    const api = new apigateway.RestApi(this, 'Api', {
      restApiName: `${props.prefix}-api-${props.stageName}`,
      deployOptions: {
        stageName: props.stageName,
      },
      retainDeployments: false,
    });

    // Define the /books resource
    const books = api.root.addResource('books');

    // Add a POST method to the /books resource
    books.addMethod('POST', new apigateway.AwsIntegration({
      service: 'sqs',
      path: `${cdk.Aws.ACCOUNT_ID}/${queue.queueName}`,
      integrationHttpMethod: 'POST',
      options: {
        credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', {
          assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
          managedPolicies: [
            iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSQSFullAccess')
          ]
        }),
        requestParameters: {
          'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'"
        },
        requestTemplates: {
          'application/json': `Action=SendMessage&MessageBody=$util.urlEncode($input.body)`
        },
        integrationResponses: [
          {
            statusCode: '200',
            responseTemplates: {
              'application/json': `{"status": "message queued"}`
            }
          },
          {
            statusCode: '500',
            responseTemplates: {
              'application/json': `{"status": "error"}`
            }
          }
        ]
      }
    }), {
      methodResponses: [
        {
          statusCode: '200'
        },
        {
          statusCode: '500'
        }
      ]
    });

    // Grant API Gateway permissions to send messages to the SQS queue
    queue.grantSendMessages(new iam.ServicePrincipal('apigateway.amazonaws.com'));

    // Define the /cars resource
    const cars = api.root.addResource('cars');

    const carSchema = {
      type: 'object',
      required: ['name', 'age'],
      properties: {
        name: { type: 'string' },
        age: { type: 'number' }
      }
    };

    // Create a model for request validation
    const requestModel = api.addModel('RequestModel', {
      contentType: 'application/json',
      modelName: 'RequestModel',
      schema: {
        schema: apigateway.JsonSchemaVersion.DRAFT4,
        type: apigateway.JsonSchemaType.OBJECT,
        title: 'MyRequest',
        required: ['name', 'age'],
        additionalProperties: false,
        properties: {
          "name": { type: apigateway.JsonSchemaType.STRING },
          "age": { type: apigateway.JsonSchemaType.INTEGER }
        }
      }
    });

    // Create a request validator
    const requestValidator = new apigateway.RequestValidator(this, 'RequestValidator', {
      restApi: api,
      validateRequestBody: true,
      requestValidatorName: 'request-validator'
    });

    // Add a POST method to the /cars resource
    cars.addMethod('POST', new apigateway.AwsIntegration({
      service: 'dynamodb',
      action: 'PutItem',
      integrationHttpMethod: 'POST',
      options: {
        credentialsRole: new iam.Role(this, 'ApiGatewayDynamoDbRole', {
          assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
          managedPolicies: [
            iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonDynamoDBFullAccess')
          ]
        }),
        requestTemplates: {
          'application/json': `{"TableName": "${carsTable.tableName}",
            "Item": {
              #foreach($entry in $input.path('$').entrySet())
              #if($entry.value != '')
                "$entry.key": { "S": "$entry.value" },
                #end
    #end
                  "carId": { "S": "$context.requestId" }
            }
        }`,
        },
        integrationResponses: [
          {
            statusCode: '200',
            responseTemplates: {
              'application/json': `{"status": "car added"}`
            },
            selectionPattern: '200',
          },
          {
            statusCode: '400',
            responseTemplates: {
              'application/json': `{"status": "client error"}`
            },
            selectionPattern: '4..',
          }
          ,
          {
            statusCode: '500',
            responseTemplates: {
              'application/json': `{"status": "server error"}`
            }
          }
        ]
      }
    }), {
      methodResponses: [
        {
          statusCode: '200'
        },
        {
          statusCode: '400'
        },
        {
          statusCode: '500'
        }
      ],
      requestModels: {
        'application/json': requestModel
      },
      requestValidator: requestValidator
    });

    // Grant API Gateway permissions to put items into the DynamoDB table
    carsTable.grantWriteData(new iam.ServicePrincipal('apigateway.amazonaws.com'));
  }
}
```