Building on AWS Using Github Copilot and AWS CDK
Published: 2021-11-08 19:21:47

In this post we are going to have a look at Github's Copilot and how we can leverage it spin up some resources using AWS CDK.
Copilot is still in technical preview. But if you already signed up for it and are lucky enough like me to have access to it, you can enable it as a simple VS Code extension.
Of course, I do not expect for the Copilot to be very mature, as it is not publicly released yet, but I believe it seems very promising and arguably it changes the way we think about software development. Just like driving assistance systems, which are not a replacement for a driver, Github Copilot will also not replace developers, but rather empowers them.
There are already many videos on youtube and blog posts explaining basic functions that Copilot automatagically generates. In this post though, we try to see how it can be used in a serverless/aws world.
As you might already know, copilot uses billions of lines of code on Github, to give you suggestion while coding. It actually is even giving me suggestions a I am writing this post. What I am going to do is to perform the same tasks I did in  [this post ](https://github.com/pedramha/cdk-crudsample) using Copilot.
Some of the things I explain here are a bit difficult to grasp in writing form, so I will explain them in a video in a later parts of this post.

So as always, first thing's first, let's initialize an empty CDK typescript project:

cdk init --language=typescript.

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

    npm install @aws-cdk/aws-ecs @aws-cdk/aws-ecs-patterns @aws-cdk/aws-ecr-assets --save

By default, CDK deploys the VPC for you, but if you want control over your VPC, go ahead and import EC2 Module and add the VPC.
Then 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";

As I write const dynamodbTable, copilot automatically generates the following part.

    new dynamodb.Table(this, "dynamodbTable", {

So it only instantiates the table using the packages I provided. However, as I go to the next line to provide the partition key and other settings for the table, it surprisingly suggested the following code:
    
    partitionKey: { name: "id", type: dynamodb.AttributeType.STRING },
    billingMode: BillingMode.PAY_PER_REQUEST,
    removalPolicy: RemovalPolicy.DESTROY,

Regarding the lambda functions, I start with the get all function. As I mentioned, it is important that you give meaningful names to your functions, because copilot will use those names to generate the code. So I only type const getAllItemsLambda, copilot generates the following for me:

      const getAllItemsLambda = new lambda.Function(this, "getAllItemsLambda", {
        runtime: lambda.Runtime.NODEJS_12_X,
        code: lambda.Code.fromAsset("lambda"),
        handler: "getAllItems.handler",
        environment: {
          TABLE_NAME: dynamodbTable.tableName,
        },
      });


Very nice so far, regarding the IAM Roles for our lambdas, as I start the name of the db, I get the following suggestions:

      dynamodbTable.grantReadWriteData(createItemsLambda);
      dynamodbTable.grantReadWriteData(getAllItemsLambda);

This is nice, though, it doesn't respect the principal of least privilege. so I amend the second line to grantReadData.

Now let's go for the API Gateway. As I start typing api, I get the following suggestion:

    const api = = new apigateway.RestApi(this, "restApi", {
        restApiName: "cdkwithcopilot",
      });

I also get the following suggestions as I type the name of the resource:

      const root = api.root;
      const getAllItemsApi = new apigateway.LambdaIntegration(getAllItemsLambda);
      root.addMethod("GET", getAllItemsApi);
    
      const createItemsApi = new apigateway.LambdaIntegration(createItemsLambda);
      root.addMethod("POST", createItemsApi);


All the resources were suggested as expected, only for the usage plan, copilot wrongly suggested api.UsagePlane instead of api.addUsagePlan . Also, it had a little problem when I added the Api Key.

Getting to the lambda functions, I was able to generate the following using copilot:


        const AWS = require('aws-sdk');
        const db = new AWS.DynamoDB.DocumentClient();
        const TablName = process.env.TABLE_NAME;
        
        export const handler = async (event: any = {}): Promise<any> => {
            const params = {
                TableName: TablName
            };
            const result = await db.scan(params).promise();
            return result.Items;
        }    


Also for the other lambda function, which creates the items:

        const AWS = require('aws-sdk');
        const db = new AWS.DynamoDB.DocumentClient();
        const TABLE_NAME = process.env.TABLE_NAME || ''; //added manually
        
        export const handler = async (event: any = {}): Promise<any> => {
            
            const params = {
                TableName: TABLE_NAME
            };
            try{
                const response = await db.scan(params).promise();
                return {status: 'success', data: response.Items};
            }
            catch(dbError){
                return{status: 'error', data: dbError};
            }
        };

Now the moment of truth: CDK synth
And worked as expected. I was truly amazed by how powerful copilot is. I even wrote a few lines of this blog post with it!
Arguably copilot is not a replacement for developers, but an amazing tool which helps us (developers) build better software.
I hope you enjoyed this post.
Thank you very much for reading.
Pedram and Copilot