logo
Published on

How to deploy NodeJs + Typescript Backend Project using AWS CDK.

AWS Cloud Deployment Kit(CDK) helps you manage your server, database etc (cloud infrastructure) using multiple programming languages such as typescript, java, python, .net, c# etc which makes it easy to use by developers, instead of writing configuration files.

Steps to configure AWS CDK in your Project

STEP 1:

Install AWS CDK globally in your PC using command

npm install -g aws-cdk

Now execute the below command, to check if it was installed properly.

cdk --version

STEP 2:

Now, setup AWS credentials with project. To do so you can either use

aws configure

OR

aws configure --profile your-profile

STEP 3:

In your project directory, create a cdk project i.e make a new folder and name it as cdk. Now, from terminal navigate to newly created folder and initialize cdk using

cd cdk
and then
 cdk init app --language typescript

Now, you will find bin/, lib/, cdk.json inside cdk folder.

Now inside cdk folder, install AWS CDK dependencies using command,

npm install @aws-cdk/aws-s3 @aws-cdk/aws-lambda @aws-cdk/aws-dynamodb
cdk folder

STEP 4:

The NodeJS project structure is as follows:- projectRoot/src/app.ts, projectRoot/src/server.ts and projectRoot/lambda.js which has the following code in it as

projectRoot/src/app.ts
import dotenv from 'dotenv';
import 'reflect-metadata';
import express, { Request, Response, Router } from 'express';
import cors from 'cors';
dotenv.config();
const app = express();
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*"); // Allow all origins or specify "http://localhost:3000"
  res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
  next();
});
app.options("*", (req, res) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
  res.sendStatus(200);
});
app.get('/', (req: Request, res: Response) => {
  try {
    res.status(200).send('Hello From Express App!')
  }
  catch (error) {
    res.status(501).send({ error: 'something went wrong' })
  }
})
module.exports = app;
projectRoot/src/server.ts
const app = require('./app');
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
    console.log(
        `App is running at http://localhost:${PORT} in %s mode,
        app.get('env')
    );
    console.log('Press CTRL-C to stop');
});
  
projectRoot/lambda.js
    const serverless = require('serverless-http');
    const app = require('./app');
    module.exports.handler = serverless(app);

STEP 5:

Now we will create lambda function in cdk and this lambda will use our project's dist folder. To do so, in tsconfig.json include "outDir": "./dist" and then execute

tsc
in terminal. This will automatically create dist folder which consists of compiled js code. but it will have dist/app.js and dist/server.js but not dist/lambda.js because tsc command only compiles ts code. so to include lambda.js inside dist folder we need to run below command

  cp lambda.js dist/lambda.js

In dist folder we also want package.json and nodemodules folder and to include this we will execute following commands

  cp package.json dist/package.json
  cd dist
  npm install

Now the dist folder will have dist/app.js and dist/lambda.js which will be used by cdk.

STEP 6:

Now we can make changes in cdk/lib/web-stack.ts to create a lambda upload that to our cdk.

  export class WebStack extends Construct {
    constructor(scope: Construct, id: string, props: WebStackProps) {
        super(scope, id);
        const STACK_ENV = process.env.STACK_ENV
        const backend = new lambda.Function(this, ${props.stage}_lambdaFunction, {
            runtime: lambda.Runtime.NODEJS_20_X,
            functionName: ${props.stage}-yourFunctionName,
            code: lambda.Code.fromAsset('../dist'),
            handler: 'lambda.handler',
            timeout: cdk.Duration.seconds(30),
            environment: {
                NODE_ENV: props.stage,
                STACK_ENV: STACK_ENV || 'dev',
            }
        });
        const api = new apigateway.LambdaRestApi(this, ${props.stage}-ApiGateway, {
            restApiName: ${props.stage}-yourRestApiName,
            handler: backend,
            proxy: false,
            deploy: true,
            deployOptions: {
                stageName: props.stage
            },
            endpointConfiguration: {
                types: [apigateway.EndpointType.EDGE]
            },
            policy: new iam.PolicyDocument({
                statements: [
                    new iam.PolicyStatement({
                        effect: iam.Effect.ALLOW,
                        actions: [
                            'execute-api:Invoke'
                        ],
                        resources: [
                            '*'
                        ],
                        principals: [
                            new iam.AnyPrincipal()
                        ]
                    })
                ]
            }),
            defaultCorsPreflightOptions: {
                allowOrigins: apigateway.Cors.ALL_ORIGINS,
                allowMethods: apigateway.Cors.ALL_METHODS,
            }
        });
        api.root.addProxy({
            defaultIntegration: new apigateway.LambdaIntegration(backend),
            anyMethod: true // "true" is the default
        });
    }
}
  
Explaination of above code:-

The code line new lambda.Function..., here you define what will be lambda function in the console and which nodejs version it will use.

code in it defines from folder we are fetching code in our case it dis dist folder

handler in it defines from that folder which file we are using to execute this lambda in our case it is dist/lambda.js file.

The code line const api = new apigateway.LambdaRestApi..., here is used to create ApiGateway in aws console. restApiName in it is what should be name of ApiGateway, prefix of the Api name is dynamic which we get as argument for eg dev-ApiGateway OR prod-ApiGateway etc.

handler defines with which lambda function RestApi is linked with and when Api is hit, it triggers this lambda function.

proxy:false defines you have manually add routes to lambda and it was proxy:true then it defines send all routes and methods to lambda.

deploy: true defines deploy the apiGateway as soon as it is created.

deployOptions defines on which stage to deplot Api for eg to deploy Api on.

endpointConfiguration here we are using type EDGE so that it sends API request to mearest POP(Point Of Presence) according to geoGraphy.

policy used in above code indicates that all resources are publicy accessible until added some restricted policy.

defaultCorsPreflightOptions this enables CORS policy which allows all methods and domains to access it.

api.root.addProxy it allows to send any requested method(GET, PUT, POST etc.) to lambda

STEP 7:

Before Deploying project to CDK, first we need to bootstrap it so that AWS enviroment is prepared fro CDK deployments. Navigate to cdk folder from terminal and then execute following commands.

cdk bootstrap

To verify what is being deployed execute the following command so that we can see CloudFormation template

cdk synth

Finally we can Deploy the Stack

cdk deploy

we can also set above commands in scripts in package.json file and run it when we build as follows

projectRoot/package.json
  "scripts": {
    "build": "tsc && npm run post-build",
    "start": "node ./dist/server.js",
    "dev": "ts-node src/server.ts",
    "post-build": "cp lambda.js dist/lambda.js && cp package.json dist/package.json && cd dist && npm install && cd ..",
    "deploy": "npm run build && cd cdk && npm run build && cdk bootstrap && cdk deploy --require-approval never"
  },

Now you can execute the following command in terminal, and it will deploy latest changes

npm run deploy

STEP 8:

Finally you can verify the deployment in AWS Console.

1. Lambda

lambda function

2. Api Gateway

Rest Api

STEP 9:

Test Api in console

select Api from ApiGateway as shown in above picture in step 8.

Deploy the Api

Deploy Api

Now Test your Route. In our case it is "/" and then hit Test button

Test Api

Now check response

Response Body

Note:- Whenever you update code, you need to reploy the code to cdk.