AWS serverless deploying nestjs as lambda

Next JS with serverless Lambda deployment

Github : https://github.com/tkssharma/blogs/tree/master/nest-js-aws-cdk-lamda-deployment

Hi Guys,

In this Blog We are going to talk about serverless deployment of Nest JS APIs I remember when i first wrote my lambda in Node JS and that time i upload my code zip and executed lambda, I was never a big fan of lambda as my approach of developing lambda was not the correct one

what i was doing wrong

  • building a single lambda for single REST APIs and map that lambda to api gateway
  • managing a lambda which is serving simple one api for my service so if i am shaving 10 end points i was having 10 lambda mapped to api gateway
  • Another issues is managing mapping at api gateway for all these REST end points and managing versions

Recently i started doing more serverless and the reason is now i don't need to struggle with all above problems

  • I can build whole microservice as a single lambda
  • i don't need to worry about api gateway mapping as we have single proxy to node js server
  • we are using using AWS CDK so its easy to deploy on AWS (other options are also good like serverless but i like AWS CDK )

So now as my previous problem are over we can use nestjs microservice to deploy on AWS

Build a simple Nest JS service

Install the CLI globally using the npm install -g command (see the Note above for details about global installs).

$ npm install -g @nestjs/cli

Basic workflow# Once installed, you can invoke CLI commands directly from your OS command line through the nest executable. See the available nest commands by entering the following:

$ nest --help

Get help on an individual command using the following construct. Substitute any command, like new, add, etc., where you see generate in the example below to get detailed help on that command:

$ nest generate --help

To create, build and run a new basic Nest project in development mode, go to the folder that should be the parent of your new project, and run the following commands:

$ nest new my-nest-project
$ cd my-nest-project
$ npm run start:dev

In your browser, open http://localhost:3000 to see the new application running. The app will automatically recompile and reload when you change any of the source files.

Setup AWS account

If you do not have it already, signup for an AWS account.

Once an account is active, create a user with the AWS IAM service and get its access key and secret access key. When that information is available create a hidden folder called ‘.aws’ in your working directory with a file name ‘credentials’ and paste the below content. Save the file once done.

[default]
aws_access_key_id = YOUR_ACCESS_KEY
aws_secret_access_key = YOUR_SECRET_ACCESS_KEY
# In VS Code terminal of a Mac machine, the commands would be:
mkdir .aws
cd .aws
touch credentials
open .

We will use AWS CDK to deploy lambda

With this configuration, Serverless will create the Lambda function main that will serve all the routes and methods of the API. so its like one service with different apis and one single Lambda

  • The next step is to install a few dependencies:

aws-serverless-express : This library enables you to utilize AWS Lambda and Amazon API Gateway to respond to web and API requests using your existing Node.js aws-lambda: AWS Lambda lets you run code without provisioning or managing servers. serverless-plugin-typescript: Serverless plugin for Typescript support that works out of the box without the need to install any other compiler or plugins. serverless-plugin-optimize: Plugin to transpile and minify your code serverless-offline plugin: Plugin to be able to test your app offline.

npm install --save aws-serverless-express
npm install --save aws-lambda
npm install --save-dev serverless-plugin-typescript
npm install --save-dev serverless-plugin-optimize
npm install --save-dev serverless-offline plugin

Nest JS application is a simple Hello world app, you can add more REST APIs and can plan to deploy this to AWS using CDK

now we need to modify nestjs service Code as we don't need http server it will be just a function

import { NestFactory } from "@nestjs/core";
import { ExpressAdapter } from "@nestjs/platform-express";
import { INestApplication } from "@nestjs/common";
import { AppModule } from "./app.module";
import { Express } from "express";

export async function createApp(
  expressApp: Express
): Promise<INestApplication> {
  const app = await NestFactory.create(
    AppModule,
    new ExpressAdapter(expressApp)
  );

  return app;
}

// async function bootstrap() {
//   const app = await NestFactory.create(AppModule);
//   await app.listen(3000);
// }
// bootstrap();

we have commented server lines here

// async function bootstrap() {
//   const app = await NestFactory.create(AppModule);
//   await app.listen(3000);
// }
// bootstrap();

We can create lambda.ts file and get app instance frok main.ts

import { Server } from "http";
import { Context } from "aws-lambda";
import { createServer, proxy, Response } from "aws-serverless-express";
import * as express from "express";
import { createApp } from "./src/main";

let cachedServer: Server;

async function bootstrap(): Promise<Server> {
  const expressApp = express();

  const app = await createApp(expressApp);
  await app.init();

  return createServer(expressApp);
}

export async function handler(event: any, context: Context): Promise<Response> {
  if (!cachedServer) {
    const server = await bootstrap();
    cachedServer = server;
  }

  return proxy(cachedServer, event, context, "PROMISE").promise;
}

Important part here is how we are adding lambda handler function and what handler function will do

export async function handler(event: any, context: Context): Promise<Response> {
  if (!cachedServer) {
    const server = await bootstrap();
    cachedServer = server;
  }

  return proxy(cachedServer, event, context, "PROMISE").promise;
}

Serverless need an entry point to compile the NestJS app to a lambda function. This entry point will be a handler object exported in the file lambda.ts. Create the lambda.ts file in the src folder of your application as shown below.

Remaining all code will be same

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Deploying Lambda to AWS using CDK

Now we just to deploy this lambda to AWS after setting up CDK AWS CDK code configuration can live is same code base, We have bin and lib folder

AWS CDK

we have created lambda Stack with construct

// import { resolve } from "path";

import * as cdk from "@aws-cdk/core";
import * as lambda from "@aws-cdk/aws-lambda";
import * as gateway from "@aws-cdk/aws-apigateway";

export class CdkNestStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const lambdaLayer = new lambda.LayerVersion(this, "BackendLayer", {
      code: lambda.Code.fromAsset("lambda/api/node_modules"),
      compatibleRuntimes: [
        lambda.Runtime.NODEJS_12_X,
        lambda.Runtime.NODEJS_10_X,
      ],
    });

    const backendLambda = new lambda.Function(this, "BackendHandler", {
      runtime: lambda.Runtime.NODEJS_12_X,
      code: lambda.Code.fromAsset("lambda/api/dist"),
      handler: "index.handler",
      layers: [lambdaLayer],
      environment: {
        NODE_PATH: "$NODE_PATH:/opt",
      },
    });
    new gateway.LambdaRestApi(this, "BackendEndpoint", {
      handler: backendLambda,
    });
  }
}

Lets import our stack and create a stack for lambda

#!/usr/bin/env node
import * as cdk from '@aws-cdk/core';
import { CdkNestStack } from '../lib/cdk-nest-stack';

const app = new cdk.App();
new CdkNestStack(app, 'CdkNestStack');

Now we can trigger basic CDK commands

cdk init
cdk synth 
cdk bootstrap 
cdk deploy
cdk destroy

cdk bootstrap : we must run this command before deploy, this is one time command to initialize platform before deployment cdk deploy with provide user friendly interface and will expose all resources its creating aws destroy - it will destroy resources created

References

Comments