Simple, Serverless CRUD with the CDK
TLDR: Hands-on tutorial for building serverless CRUD applications with AWS CDK. Learn to set up infrastructure as code, create Lambda functions, and implement authenticated API endpoints with practical examples.
This post provides some basic information on the CDK as well as some code examples to get you started.
How do I set up the AWS CDK?
The first step is to get the CDK installed. This requires:
- Installing Node.js
- Setting up an AWS account
- Actually installing the CDK
- Review the CDK basics.
How do I use the CDK CLI?
The two main commands for the CDK cli are cdk synth and cdk deploy. synth
will output a template. deploy will generate the template and deploy it.
What does the base CDK template look like?
The AWS docs do a pretty good job of explaining how to get off the ground. The focus of this guide is providing a backend for simple CRUD operations.
The amplify-base-cdk.py describes a base level stack for supporting CRUD operations. Comments are included to explain what's going on.
Why should I use the CDK?
There are a few reasons why the CDK is awesome!
You get to work in a language you're familiar with
YAML/JSON can be challenging to work with. There are tools like cfn-lint, but it can only go so far in linting a YAML file.
Using a language of your choice makes life easier for you and you get extensive type checking, linting, and
validation. synth and deploy ensure that you have a valid template.
Declarative with use of programming language features
The CDK is highly declarative. Related to higher level constructs, rather than having to explicitly define a policy and attach it to a role, you can do something like:
table.grant_read_write_data(function)
The CDK defines all sorts of interfaces to make this possible. All that's required is the argument passed to
grant_read_write_table adheres to an interface that exposes a IAM principal.
You can also make use of programming language features such as if/else and looping. If you know all of your Lambda functions will use the Python 3.8 runtime, have the same handler function definition, and make use of a table you could create:
def create_python_function(function_name, function_path):
parent_path = Path(__file__).parent.absolute()
return function = lambda_.Function(self, function_name,
code=lambda_.Code.from_asset(str(Path(parent_path, function_path))),
runtime=lambda_.Runtime.PYTHON_3_8,
handler='handler.handler',
environment={'TABLE': table.table_name})
Bye bye boilerplate!
Higher Level Constructs
The CDK represents CloudFormation resources as constructs. Raw CloudFormation is quite verbose. For the resources that make use of higher level constructs, things are quieted down. For example in the CDK a table may look like:
table = dynamodb.Table(self, 'Table',
partition_key=dynamodb.Attribute(name='pk', type=dynamodb.AttributeType.STRING),
sort_key=dynamodb.Attribute(name='sk', type=dynamodb.AttributeType.STRING))
table.add_global_secondary_index(
partition_key=dynamodb.Attribute(name='sk', type=dynamodb.AttributeType.STRING),
sort_key=dynamodb.Attribute(name='gsi1sk', type=dynamodb.AttributeType.STRING),
index_name='gsi1')
and a corresponding YAML file may look like:
myDynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
-
AttributeName: "Album"
AttributeType: "S"
-
AttributeName: "Artist"
AttributeType: "S"
-
AttributeName: "Sales"
AttributeType: "N"
-
AttributeName: "NumberOfSongs"
AttributeType: "N"
KeySchema:
-
AttributeName: "Album"
KeyType: "HASH"
-
AttributeName: "Artist"
KeyType: "RANGE"
ProvisionedThroughput:
ReadCapacityUnits: "5"
WriteCapacityUnits: "5"
TableName: "myTableName"
GlobalSecondaryIndexes:
-
IndexName: "myGSI"
KeySchema:
-
AttributeName: "Sales"
KeyType: "HASH"
-
AttributeName: "Artist"
KeyType: "RANGE"
Projection:
NonKeyAttributes:
- "Album"
- "NumberOfSongs"
ProjectionType: "INCLUDE"
ProvisionedThroughput:
ReadCapacityUnits: "5"
WriteCapacityUnits: "5"