BASIC AWS CDK Backend API
Published: 2021-07-14 19:14:00

---
layout: post
title: BASIC AWS CDK Backend API
date: 2021-07-14 21:14 +0200
---

In this post, you learn how to use AWS CDK to create a basic yet powerful backend in AWS. The components used for this application are, AWS API Gateway which receives http requests as an entrance to our application, AWS Lambda where we implement our business logic, Dynamodb a NoSQL Database where we store our data.

First thing's first, let's initialize an empty CDK typescript project:
cdk init app --language=typescript.

Then we go to package.json to declare the dependencies for our project.
Add the following modulesto the dependencies:

    "@aws-cdk/core": "*",
    "@aws-cdk/aws-apigateway": "*",,
    "@aws-cdk/aws-lambda": "*",,
    "@aws-cdk/aws-dynamodb": "*",

This is an optional step, but go ahead and run "npm install" in the root directory of your project. This will download the dependencies and give you intellisense which comes very handy.
The go to the lib folder and the typescript file for your application stack.

Add the following import statements to before class declaration:

    import cdk = require("@aws-cdk/core");
    import apigateway = require("@aws-cdk/aws-apigateway");
    import dynamodb = require("@aws-cdk/aws-dynamodb");
    import lambda = require("@aws-cdk/aws-lambda");
    import { RemovalPolicy } from "@aws-cdk/core";
    import { create } from "domain";
    import { BillingMode } from "@aws-cdk/aws-dynamodb";

In order to create our database, add the following:

    const dynamoTable=new dynamodb.Table(this,'mytestDB',{
      partitionKey:{
        name:'itemId',
        type:dynamodb.AttributeType.STRING
      },
        removalPolicy:RemovalPolicy.RETAIN
    });

Here we defined a partition key 'itemId' of type string, and the removal policy of our database. As data is very sensitive (of course, not for this demo but in real life), we chose the Retain policy if our stack gets destroyed. You could also chose Snapshot, or destory policy if you like.
Getting to the business logic of our application, we declare our lambda functions.
Firstly, we create a get all function, which returns everything in the databse.

    const getAll = new lambda.Function(this, 'getAllitems',{
      code: new lambda.AssetCode('./src'),
      handler:'get-all.handler',
      runtime:lambda.Runtime.NODEJS_10_X,
      environment:{
        TABLE_NAME: dynamoTable.tableName,
        PRIMARY_KEY:'itemId'
      }
    });
Firstly, we tell cdk where the code for our function is, (src folder), then the handler inside that code, the runtime and some environment variables where we pass in our dynamodb table name and the pratition key.

Than we add one more lambda function which creates item in our databse as follows.

    const createLambda = new lambda.Function(this, 'createItem',{
      code: new lambda.AssetCode('./src'),
      handler:'create.handler',
      runtime:lambda.Runtime.NODEJS_10_X,
      environment:{
        TABLE_NAME: dynamoTable.tableName,
        PRIMARY_KEY:'itemId'
      },
    });

As of now, our lambdas cannot connect to the database, as the IAM role for them is not yet created. Using the following command, we grant access to our lambdas, while respecting the prinicple of least privilege.

    dynamoTable.grantReadData(getAll);
    dynamoTable.grantReadWriteData(createLambda);

Having created our persistance layer and our business logic layer, we go ahead and create the gateway to our application. As we would like to serve our content via REST API, we declare an REST API gateway as follows:
   
    const api = new apigateway.RestApi(this,' testApi',{
      restApiName:'my test api'
    });

Then we create a root API on this API Gateway. When a request hits this 'root' api, they can get all items in our db. Declaring the api is not sufficient, we need to connect it with our lambda. for that we use the 
LambdaIntegration method of the api gateway. API gateway has many integration types that are worth looking.

    const rootApi=api.root.addResource('root');
    const getAllApi=new apigateway.LambdaIntegration(getAll);
    rootApi.addMethod('GET',getAllApi);

Using the following code we can also create items in our databse. As the Create item in database translates to POST in REST, we add this functionality on POST method of our API.

    const createApi=rootApi.addResource('create');
    const createApiIntegration=new apigateway.LambdaIntegration(createLambda);
    createApi.addMethod('POST', createApiIntegration);

As we are security concious developers, we don't provide apis without auhtentication in the wild internet. Using the follwing code, we add throttling to our api, which means that our costs won't skyrocket unexpectedly. We also add an apikey to our api gateway. This is, of course, a very basic authentication mechanism, but for the purpose of this demo is sufficient. For production grade applications you might want to have a look at AWS Cognito.

    const plan = api.addUsagePlan('UsagePlan', {
      name:'EASY',
      throttle:{
        rateLimit:20,
        burstLimit:2
      }
    });
    const key =api.addApiKey('ApiKey');
    plan.addApiKey(key);

Now we get to the actual code of our lambda functions. Inside the root folder, create a folder called 'src'. Then create a file called 'create.ts'.

Add the follwing to create.ts

    const AWS = require('aws-sdk');
    const db = new AWS.DynamoDB.DocumentClient();
    const TABLE_NAME = process.env.TABLE_NAME || '';
    const PRIMARY_KEY = process.env.PRIMARY_KEY || '';
    export const handler = async (event: any={}) : Promise <any> => {
    
       const item =typeof event.body == 'object' ? event.body :JSON.parse(event.body);
       const ID = String.fromCharCode(65 + Math.floor(Math.random()*26));
       item[PRIMARY_KEY] = ID;
        const params = {
            TableName: TABLE_NAME,
            Item:item
        };
    
      try {
        await db.put(params).promise();
        return { statusCode: 200, body: 'success' };
      } catch (dbError) {
        return { statusCode: 500, body: JSON.stringify(dbError)};
      }
    };

The important part of this code is the ID. Dynamodb is designed differently from traditional relational dbs, and therefore you cannot have an identity field which is incremented by the db. You have to provide a unique ID when you insert items in your db.

Having declared our create function, we then create our get all function. Add a file called get-all.ts and add the following to it.

    const AWS = require('aws-sdk');
    const db = new AWS.DynamoDB.DocumentClient();
    const TABLE_NAME = process.env.TABLE_NAME || '';
    
    export const handler = async () : Promise <any> => {
    
      const params = {
        TableName: TABLE_NAME
      };
    
      try {
        const response = await db.scan(params).promise();
        return { statusCode: 200, body: JSON.stringify(response.Items) };
      } catch (dbError) {
        return { statusCode: 500, body: JSON.stringify(dbError)};
      }
    };

Now we are ready to verify our cdk stack. First run 'npm run build' which compiles typescript to javascript.
Then run cdk synth. This should give a cloud formation template. If it returns an error, go to previous steps and verify your work.
Before you can deploy this applicaion, you need to bootsrap your environment. To do that, run the following.


    cdk bootstrap aws://youraccountID/yourregion

    set CDK_NEW_BOOTSTRAP=1
    cdk bootstrap aws://youraccountID/yourregion


Now you are ready to deploy your cdk app. 
    
    cdk deploy

Some more useful cdk commands:

    cdk ls //lists the stacks in your application
    cdk diff //shows the difference of your current deployed stack and your local stack
    cdk doctor //*kind of* verifies your cdk stack for warnings

That is it for a very minimal backend in aws. Of course, you can add more functions, to do update and delete. But as they are very similar, they are left out in this demo.


The source code on github: https://github.com/pedramha/cdk-crudsample


The link to this video can be found at:


Part1:    https://www.youtube.com/watch?v=uhE3Z2lGGXQ


Part2:    https://www.youtube.com/watch?v=5oKEti1W5BQ