Introduction#
GitHub this shows how to integrate apigw with different aws services such as Lambda, Stepfunctions, and SQS. It also covers:
- Integration targets: lambda, stepfunctions, and sqs
- Deployment options: multiple stages, access log groups
To integrate apigw with targets, we need:
- Role for apigw
- Request mapping: proxy, request parameters, request templates
- Response mapping: method response, integration response, reponse template
Lambda Backend#
create a lambda for backend
const func = new aws_lambda.Function(this, 'ProcessOrderLambda', {functionName: 'ProcessOrderLambda',code: aws_lambda.Code.fromAsset(path.join(__dirname, './../lambda')),handler: 'index.handler',runtime: aws_lambda.Runtime.PYTHON_3_8})
lambda should return a corret header format to support proxy mode
import uuiddef handler(event, context):return {"statusCode": 200,"headers": {"Access-Control-Allow-Origin": "*","Access-Control-Allow-Headers": "Content-Type","Access-Control-Allow-Methods": "OPTIONS,GET",},"body": {"id": str(uuid.uuid4()),"name": "haimtran","message": "hello lambda api",},}
Role for ApiGw#
role for APIGW to invoke the lambda and put logs
const role = new aws_iam.Role(this, 'RoleForApiGwInvokeLambda', {roleName: 'ApiGwInvokeLambda',assumedBy: new aws_iam.ServicePrincipal('apigateway.amazonaws.com')})role.addToPolicy(new aws_iam.PolicyStatement({effect: Effect.ALLOW,actions: ['lambda:InvokeFunction'],resources: [func.functionArn]}))role.addToPolicy(new aws_iam.PolicyStatement({effect: Effect.ALLOW,actions: ['logs:CreateLogGroup','logs:CreateLogStream','logs:DescribeLogGroups','logs:DescribeLogStreams','logs:PutLogEvents','logs:GetLogEvents','logs:FilterLogEvents'],resources: ['*']}))
ApiGw Prod Stage#
access log group for production stage
const devLogGroup = new aws_logs.LogGroup(this, 'DevLogGroup', {logGroupName: 'DevLogGroup',removalPolicy: RemovalPolicy.DESTROY})
create an apigw and production stage deployment
this.apigw = new aws_apigateway.RestApi(this, 'ApiGwIntegration', {restApiName: 'ApiGwIntegration',deployOptions: {stageName: 'dev',accessLogDestination: new aws_apigateway.LogGroupLogDestination(devLogGroup),accessLogFormat: aws_apigateway.AccessLogFormat.jsonWithStandardFields()}})
create api resource
const book = this.apigw.root.addResource('lambda')
Development Stage#
access log group for development stage
const devLogGroup = new aws_logs.LogGroup(this, 'ApiAccessLogGroup', {logGroupName: 'DevLogGroupAccessLog'})
deployment stage
const deployment = new aws_apigateway.Deployment(this, 'Deployment', {api: apiGw})new aws_apigateway.Stage(this, 'DevStage', {stageName: 'dev',deployment,dataTraceEnabled: true,accessLogDestination: new aws_apigateway.LogGroupLogDestination(devLogGroup),accessLogFormat: aws_apigateway.AccessLogFormat.jsonWithStandardFields()})
Lambda Integration#
integrate apigw with the lambda function
book.addMethod('GET',new aws_apigateway.LambdaIntegration(func, {proxy: false,allowTestInvoke: false,credentialsRole: role,// api-input-mappassthroughBehavior: PassthroughBehavior.WHEN_NO_TEMPLATES,requestParameters: {},requestTemplates: {},// api-output-mapintegrationResponses: [{statusCode: '200',responseTemplates: {'application/json': fs.readFileSync(path.resolve(__dirname, './../lambda/response-template'),{ encoding: 'utf-8' })}}]}),{// no need for lambda-proxy truemethodResponses: [{statusCode: '200'}]})
then we want apigw to intercept the response from backend before return to client by using a model/schema as follow. This is called models and mapping template for payload.
#set($inputRoot = $input.path('$')){"id": "$inputRoot.body.id","message": "$inputRoot.body.message"}
SQS Integration#
create a qeuue
const queue = new aws_sqs.Queue(this, 'ApiSqsQueue', {queueName: 'ApiSqsQueue'})
for for apigw to send messages to queue
role.addToPolicy(new aws_iam.PolicyStatement({effect: Effect.ALLOW,resources: [queue.queueArn],actions: ['sqs:SendMessage']}))
create an api resource
const resource = props.apigw.root.addResource('queue')
integrate apigw with the queue.
// integrate apigw with sqsresource.addMethod('POST',new aws_apigateway.AwsIntegration({service: 'sqs',// exclusive pathaction: 'StartExecution',options: {credentialsRole: role,passthroughBehavior: PassthroughBehavior.WHEN_NO_TEMPLATES,requestParameters: {'integration.request.header.Content-Type': `'application/x-www-form-urlencoded'`},requestTemplates: {'application/json': fs.readFileSync(path.resolve(__dirname, './../lambda/request-template'),{ encoding: 'utf-8' })},integrationResponses: [{statusCode: '200'}]}}),// method response{methodResponses: [{statusCode: '200'}]})
apigw needs to transform the request to match the request format of sqs here by using a request template (mapping), and add a header content-type parameter for POST method. That's whey we need request-template here.
Action=SendMessage&MessageBody=$util.urlEncode("$method.request.querystring.message")
EventBridge Integration#
role for apigw to put events to the default event bus
role.addToPolicy(new aws_iam.PolicyStatement({effect: Effect.ALLOW,resources: ['*'],actions: ['events:PutEvents']}))
integrate the sqs queue with apigw. we need a request template to transform client requests into correct request format of eventbridge
// apigw add resourceconst resource = apigw.root.addResource('event')// integrate apigw with sqsresource.addMethod('POST',new aws_apigateway.AwsIntegration({service: 'events',action: 'PutEvents',options: {credentialsRole: role,passthroughBehavior: PassthroughBehavior.WHEN_NO_TEMPLATES,requestParameters: {},requestTemplates: {'application/json': fs.readFileSync(path.resolve(__dirname, './../lambda/request-event-template'),{ encoding: 'utf-8' })},integrationResponses: [{statusCode: '200'}]}}),// method response{methodResponses: [{statusCode: '200'}]})
here is my lazy template
#set($context.requestOverride.header.X-Amz-Target = "AWSEvents.PutEvents")#set($context.requestOverride.header.Content-Type = "application/x-amz-json-1.1")#set($inputRoot = $input.path('$')){"Entries": [{"Resources": ["1234"],"Detail": "{ \"key1\": \"value1\", \"key2\": \"value2\" }","DetailType": "dev","EventBusName": "default","Source": "apigateway"}]}
Stepfunction Integration#
role for apigw to invoke stepfunctions
role.addToPolicy(new aws_iam.PolicyStatement({effect: Effect.ALLOW,resources: [stateMachine.stateMachineArn],actions: ['states:StartExecution']}))
create apigw with no default deployment in cdk
const apigw = new aws_apigateway.RestApi(this, 'ApiGw', {restApiName: 'ApiGwSetpfunction',deploy: false})
stepfunction integration
apigw.root.addMethod('POST',new aws_apigateway.AwsIntegration({service: 'states',// exclusive pathaction: 'StartExecution',options: {credentialsRole: role,passthroughBehavior: PassthroughBehavior.WHEN_NO_TEMPLATES,requestParameters: {},requestTemplates: {'application/json': `#set($inputRoot = $input.path('$')){"input": "$util.escapeJavaScript($input.json('$'))","stateMachineArn": "${stateMachine.stateMachineArn}"}`},integrationResponses: [{statusCode: '200'// responseParameters: {},// responseTemplates: {},// selectionPattern: "",}]}}),{methodResponses: [{statusCode: '200'}]})
deployment and starge
// deploymentconst deployment = new aws_apigateway.Deployment(this, 'deployment', {api: apigw})// dev stagenew aws_apigateway.Stage(this, 'DevStage', {stageName: 'dev',deployment: deployment,accessLogDestination: new aws_apigateway.LogGroupLogDestination(logGroup),accessLogFormat: aws_apigateway.AccessLogFormat.jsonWithStandardFields()})
option 1. the post request body should be
{"input": "{}","name": "MyExecution","stateMachineArn": "arn:aws:states:xxx"}
option 2. using request template
#set($inputRoot = $input.path('$')){"input": "$util.escapeJavaScript($input.json('$'))","stateMachineArn": "arn:aws:states:xxx"}
then body request
{"input": "{}","name": "execution-1"}
Conclusion#
- Role for apigw
- Multiple stage deployment
- Access log group
- Response mapping - integration response setting (aws console)
- Request mapping - integration request setting (aws console)