Deploy Lambda with API Gateway with different options AWS-CDK Part-5
In this Blog we will cover all these Topics
- Build a simple Lambda function
- Attach REST API gateway to the lambda using v1
- Attach REST API gateway to the lambda using v2
- Attach Proxy API gateway to the lambda
We are going to build api gateway but using different approach and options one is just api proxy gateway pointing to lambda function Another is REST Gateway with REST APIs exposed and calling different lambda functions from Gateway
This is how our whole stack Looks like, lets take a look and understand each and every blocks for these constructs in out Stack
API Gateway just as Proxy
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as path from 'path';
export interface LambdaProps extends cdk.StackProps {
stage: string;
}
export class LambdaAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: LambdaProps) {
super(scope, id, props);
const { stage } = props;
const apiGetUsersLambdaFn = new cdk.aws_lambda.Function(this, `api-get-user-${stage}`, {
functionName: `api-get-user-lambda-${stage}`,
runtime: cdk.aws_lambda.Runtime.NODEJS_16_X,
memorySize: 1024,
logRetention: cdk.aws_logs.RetentionDays.FIVE_DAYS,
environment: {
stage,
},
handler: 'index.handler',
code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, '..', 'src')),
initialPolicy: [
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
actions: ['s3:*'],
resources: ['*'],
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
actions: ['sns:*'],
resources: ['*'],
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
actions: ['dynamodb:*'],
resources: ['*'],
}),
],
});
userDynamoTable.grantReadWriteData(apiGetUsersLambdaFn);
userUploadsS3Bucket.grantReadWrite(apiGetUsersLambdaFn);
// API GW
const apiGw = new cdk.aws_apigateway.LambdaRestApi(this, `users-api-gw`, {
handler: apiGetUsersLambdaFn,
deploy: true,
proxy: true,
binaryMediaTypes: ['*/*'],
deployOptions: {
stageName: stage,
},
});
new cdk.CfnOutput(this, `apiGetUsersLambdaFn`, {
exportName: `apiGetUsersLambdaFn--arn`,
value: apiGetUsersLambdaFn.functionArn,
});
new cdk.CfnOutput(this, `user-api-gateway`, {
exportName: `user-api--gateway-arn`,
value: apiGw.restApiName,
});
}
}
In this example we are just creating Proxy gateway
const apiGw = new cdk.aws_apigateway.LambdaRestApi(this, `users-api-gw`, {
handler: apiGetUsersLambdaFn,
deploy: true,
proxy: true,
binaryMediaTypes: ['*/*'],
deployOptions: {
stageName: stage,
},
});
Here proxy
is true and handler is apiGetUsersLambdaFn
single lambda function
Building Gateway as REST API Gateway
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as path from 'path';
export interface LambdaProps extends cdk.StackProps {
stage: string;
}
export class LambdaAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: LambdaProps) {
super(scope, id, props);
const { stage } = props;
const userUploadsS3Bucket = new cdk.aws_s3.Bucket(this, `user-api-upload-${stage}`, {
bucketName: `user-api-upload-${stage}`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
const userDynamoTable = new dynamodb.Table(this, `users-table-${stage}`, {
tableName: `api-users-table-${stage}`,
partitionKey: { name: 'user_id', type: dynamodb.AttributeType.STRING },
sortKey: { name: 'created_at', type: dynamodb.AttributeType.NUMBER },
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
const apiGetUsersLambdaFn = new cdk.aws_lambda.Function(this, `api-get-user-${stage}`, {
functionName: `api-get-user-lambda-${stage}`,
runtime: cdk.aws_lambda.Runtime.NODEJS_16_X,
memorySize: 1024,
logRetention: cdk.aws_logs.RetentionDays.FIVE_DAYS,
environment: {
stage,
},
handler: 'index.handler',
code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, '..', 'src')),
initialPolicy: [
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
actions: ['s3:*'],
resources: [userUploadsS3Bucket.bucketArn],
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
actions: ['sns:*'],
resources: ['*'],
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
actions: ['dynamodb:*'],
resources: [userDynamoTable.tableArn],
}),
],
});
const apiPutUsersLambdaFn = new cdk.aws_lambda.Function(this, `api-put-user-${stage}`, {
functionName: `api-put-user-lambda-${stage}`,
runtime: cdk.aws_lambda.Runtime.NODEJS_16_X,
memorySize: 1024,
logRetention: cdk.aws_logs.RetentionDays.FIVE_DAYS,
environment: {
stage,
},
handler: 'index.handler',
code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, '..', 'src')),
initialPolicy: [
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
actions: ['s3:*'],
resources: [userUploadsS3Bucket.bucketArn],
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
actions: ['sns:*'],
resources: ['*'],
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
actions: ['dynamodb:*'],
resources: [userDynamoTable.tableArn],
}),
],
});
const apiPostUsersLambdaFn = new cdk.aws_lambda.Function(this, `api-post-user-${stage}`, {
functionName: `api-post-user-lambda-${stage}`,
runtime: cdk.aws_lambda.Runtime.NODEJS_16_X,
memorySize: 1024,
logRetention: cdk.aws_logs.RetentionDays.FIVE_DAYS,
environment: {
stage,
},
handler: 'index.handler',
code: cdk.aws_lambda.Code.fromAsset(path.join(__dirname, '..', 'src')),
initialPolicy: [
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
actions: ['s3:*'],
resources: [userUploadsS3Bucket.bucketArn],
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
actions: ['sns:*'],
resources: ['*'],
}),
new cdk.aws_iam.PolicyStatement({
effect: cdk.aws_iam.Effect.ALLOW,
actions: ['dynamodb:*'],
resources: [userDynamoTable.tableArn],
}),
],
});
userDynamoTable.grantReadWriteData(apiGetUsersLambdaFn);
userUploadsS3Bucket.grantReadWrite(apiGetUsersLambdaFn);
userDynamoTable.grantReadWriteData(apiPostUsersLambdaFn);
userUploadsS3Bucket.grantReadWrite(apiPostUsersLambdaFn);
userDynamoTable.grantReadWriteData(apiPutUsersLambdaFn);
userUploadsS3Bucket.grantReadWrite(apiPutUsersLambdaFn);
// Integrate the Lambda functions with the API Gateway resource
const getAllUserIntegration = new cdk.aws_apigateway.LambdaIntegration(apiGetUsersLambdaFn);
const postUserIntegration = new cdk.aws_apigateway.LambdaIntegration(apiPostUsersLambdaFn);
const putUserIntegration = new cdk.aws_apigateway.LambdaIntegration(apiPutUsersLambdaFn);
// Create an API Gateway resource for each of the CRUD operations
const api = new cdk.aws_apigateway.RestApi(this, `users-apis-${stage}`, {
restApiName: `user Service ${stage}`,
});
const user = api.root.addResource('users');
user.addMethod('GET', getAllUserIntegration);
user.addMethod('POST', postUserIntegration);
user.addMethod('PUT', putUserIntegration);
new cdk.CfnOutput(this, `apiGetUsersLambdaFn`, {
exportName: `apiGetUsersLambdaFn--arn`,
value: apiGetUsersLambdaFn.functionArn,
});
new cdk.CfnOutput(this, `apiPutUsersLambdaFn`, {
exportName: `apiPutUsersLambdaFn--arn`,
value: apiPutUsersLambdaFn.functionArn,
});
new cdk.CfnOutput(this, `apiPostUsersLambdaFn`, {
exportName: `apiPostUsersLambdaFn--arn`,
value: apiPostUsersLambdaFn.functionArn,
});
new cdk.CfnOutput(this, `userDynamoTable`, {
exportName: `userDynamoTable--arn`,
value: userDynamoTable.tableArn,
});
new cdk.CfnOutput(this, `userUploadsS3Bucket`, {
exportName: `userUploadsS3Bucket--arn`,
value: userUploadsS3Bucket.bucketArn,
});
new cdk.CfnOutput(this, `user-api-gateway`, {
exportName: `user-api--gateway-arn`,
value: api.restApiName,
});
}
}
In this example we are creating REST API gateway and adding REST API resources to it
// Create an API Gateway resource for each of the CRUD operations
const api = new cdk.aws_apigateway.RestApi(this, `users-apis-${stage}`, {
restApiName: `user Service ${stage}`,
});
// Integrate the Lambda functions with the API Gateway resource
const getAllUserIntegration = new cdk.aws_apigateway.LambdaIntegration(apiGetUsersLambdaFn);
const postUserIntegration = new cdk.aws_apigateway.LambdaIntegration(apiPostUsersLambdaFn);
const putUserIntegration = new cdk.aws_apigateway.LambdaIntegration(apiPutUsersLambdaFn);
const user = api.root.addResource('users');
user.addMethod('GET', getAllUserIntegration);
user.addMethod('POST', postUserIntegration);
user.addMethod('PUT', putUserIntegration);
When we deploy this stack we will have users resource created on API Gateway with all different methods mapped to different HTTP methods
- api-gateway-url/${stage}/users -- getAllUserIntegration HTTP GET
- api-gateway-url/${stage}/users -- postUserIntegration HTTP POST
- api-gateway-url/${stage}/users -- putUserIntegration HTTP PUT
we can also control CORS configuration for API Gateway
const api = new apigateway.RestApi(this, 'api', {
description: 'example api gateway',
deployOptions: {
stageName: 'dev',
},
defaultCorsPreflightOptions: {
allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key'],
allowMethods: ['OPTIONS', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowCredentials: true,
allowOrigins: ['http://localhost:3000'],
},
});
new cdk.CfnOutput(this, 'apiUrl', { value: api.url });
Building Gateway as REST API Gateway V2
Now we can check V2 for same approach Now these REST APIs are more simplified using stack with V2 APIs
import { CorsHttpMethod, HttpApi, HttpMethod } from '@aws-cdk/aws-apigatewayv2-alpha';
import { HttpLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations-alpha';
import * as lambda from 'aws-cdk-lib/aws-lambda';
This approach is little more simplified and we have to write less code for adding REST APIs resources to the gateway
import {
CorsHttpMethod,
HttpApi,
HttpMethod,
} from '@aws-cdk/aws-apigatewayv2-alpha';
import { HttpLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations-alpha';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as cdk from 'aws-cdk-lib';
import * as path from 'path';
export class CdkStarterStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const httpApi = new HttpApi(this, 'cors-demo-api', {
description: 'API for CORS demo',
corsPreflight: {
allowHeaders: [
'Content-Type',
'X-Amz-Date',
'Authorization',
'X-Api-Key',
],
allowMethods: [
CorsHttpMethod.OPTIONS,
CorsHttpMethod.GET,
CorsHttpMethod.POST,
CorsHttpMethod.PUT,
CorsHttpMethod.PATCH,
CorsHttpMethod.DELETE,
],
allowCredentials: true,
allowOrigins: ['http://localhost:3000'],
// 👇 optionally cache responses to preflight requests
// maxAge: cdk.Duration.minutes(5),
},
});
// 👇 create get-todos Lambda
const getTodosLambda = new lambda.Function(this, 'get-todos', {
runtime: lambda.Runtime.NODEJS_16_X,
handler: 'index.handler',
code: lambda.Code.fromAsset(path.join(__dirname, '/../src')),
});
// 👇 add route for GET /todos
httpApi.addRoutes({
path: '/todos',
methods: [HttpMethod.GET],
integration: new HttpLambdaIntegration(
'get-todos-integration',
getTodosLambda
),
});
// 👇 create delete-todos Lambda
const deleteTodoLambda = new lambda.Function(this, 'delete-todo', {
runtime: lambda.Runtime.NODEJS_16_X,
handler: 'index.handler',
code: lambda.Code.fromAsset(path.join(__dirname, '/../src')),
});
// 👇 add route for DELETE /todos/{todoId}
httpApi.addRoutes({
path: '/todos/{todoId}',
methods: [HttpMethod.DELETE],
integration: new HttpLambdaIntegration(
'delete-todo-integration',
deleteTodoLambda
),
});
// 👇 add an Output with the API Url
new cdk.CfnOutput(this, 'apiUrl', {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
value: httpApi.url!,
});
}
}
This is the major change and we are able to add resources and routes using simple methods
httpApi.addRoutes({
path: '/todos',
methods: [HttpMethod.GET],
integration: new HttpLambdaIntegration('get-todos-integration', getTodosLambda),
});
Deploy API Gateway with different approaches
cdk synth
cdk bootstrap
cdk deploy
cdk destroy
When we synthesize our CloudFormation stack, it gets generated in the cdk.out directory. This is also where the asset files for our Lambda functions are stored. Let’s run the synth command to generate the lambda assets:
npx aws-cdk synth
If we now take a look at the assets folder in the cdk.out directory, we can see that our Lambda function’s code has been compiled down to JavaScript.
Deploy the Lambda function
The next step is to bootstrap an environment. This action is required only if it is the first time you want to deploy with the CDK; you can skip this if you have already done it before.
This command will create a stack that includes resources used for the toolkit’s operation, like an S3 bucket to store templates and assets during the deployment process.
cdk bootstrap
⏳ Bootstrapping environment aws://123456789012/eu-west-1...
Once done, we can deploy our app:
``
```sh
npx aws-cdk deploy
Cleanup
To delete the stack from your account, run the destroy command:
npx aws-cdk destroy