DEV Community

Shaikh Al Amin
Shaikh Al Amin

Posted on

2

How to deploy Node.js Nest.js project on AWS lamda serverless

N:B Setup necessary VPC configurations, public and private subnet, provide necessary permission of your AWS access and secret key and more importantly the lambda security group outbound rules:

  1. 443 Outbound rules should be forwarded to 0.0.0.0/0
  2. Any 5432/Postgres Outbound rules should be forwarded be to postgres-security-group
  3. For Postgres security group inbound rule accepts incoming from Lamda security groups

Create Serverless yml in project root:

service: backend-api

frameworkVersion: '^3.0.0'

useDotenv: true
configValidationMode: error

provider:
  name: aws
  region: us-east-2
  runtime: nodejs20.x
  stage: ${opt:stage, 'dev'}
  deploymentMethod: direct
  logRetentionInDays: 7

  vpc:
    securityGroupIds:
      - sg-13f39e24a9d
      - sg-87037c0f81
    subnetIds:
      - subnet-d40bc57
      - subnet-45gf564g3454

  ecr:
    scanOnPush: true
    images:
      backend-api-image:
        path: .
        file: Dockerfile

  environment:
    NODE_ENV: ${env:NODE_ENV}
    DATABASE_URL: ${env:DATABASE_URL}
    JWT_TOKEN_SECRET: ${env:JWT_TOKEN_SECRET}
    API_BACKEND_AWS_REGION: ${env:STUDY_BUDS_AWS_REGION}
    API_BACKEND_AWS_ACCESS_KEY: ${env:STUDY_BUDS_AWS_ACCESS_KEY}
    API_BACKEND_AWS_SECRETE_KEY: ${env:STUDY_BUDS_AWS_SECRETE_KEY}
    AWS_BUCKET_NAME: ${env:AWS_BUCKET_NAME}
    AWS_CLOUD_FRONT_URL: ${env:AWS_CLOUD_FRONT_URL}
    AWS_SMTP_HOST: ${env:AWS_SMTP_HOST}
    AWS_SMTP_USER: ${env:AWS_SMTP_USER}
    AWS_SMTP_PASS: ${env:AWS_SMTP_PASS}
    AWS_FROM_EMAIL: ${env:AWS_FROM_EMAIL}
    AI_BACKEND_URL: ${env:AI_BACKEND_URL}
    CORS_ALLOWED_HOSTS: ${env:CORS_ALLOWED_HOSTS}
    FRONTEND_BASE_URL: ${env:FRONTEND_BASE_URL}
    MAIL_HOST: ${env:MAIL_HOST}
    MAIL_USER: ${ env:MAIL_USER }
    MAIL_PASSWORD: ${env:MAIL_PASSWORD}
    MAIL_FROM_NAME: ${env:MAIL_FROM_NAME}
    MAIL_FROM_ADDRESS: ${env:MAIL_FROM_ADDRESS}

  apiGateway:
    binaryMediaTypes:
      - '*/*'
custom:
  prune:
    automatic: true
    number: 2

functions:
  main:
    image:
      name: backend-api-image
      command:
        - dist/src/serverless.handler
      entryPoint:
        - '/lambda-entrypoint.sh'
    memorySize: 1024
    timeout: 330
    url: true
    provisionedConcurrency: 0
    events:
      - http:
          method: ANY
          path: /
      - http:
          method: ANY
          path: '{proxy+}'

Enter fullscreen mode Exit fullscreen mode

Install below two packages:

npm i @codegenie/serverless-express
npm i -D @types/aws-lambda
Enter fullscreen mode Exit fullscreen mode

Create serverless.ts inside src/serverless.ts

import { NestFactory, Reflector } from '@nestjs/core';
import { AppModule } from './app.module';
import serverlessExpress from '@codegenie/serverless-express';
import { Callback, Context, Handler } from 'aws-lambda';
import { RequestMethod, ValidationPipe } from '@nestjs/common';
import helmet from 'helmet';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { ResponseTransformInterceptor } from './common/interceptor/global-response-interceptor';
import { RolePermissionsSeederService } from './modules/v1/user/seed/role-permissions.seeder.service';
import { SeedService } from './modules/v1/character/seeder/seeder.service';
let server: Handler;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const allowedHosts = (process.env.CORS_ALLOWED_HOSTS as string) || '*';

  app.enableCors({
    origin: allowedHosts.split(','),
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
    credentials: true,
  });

  app.setGlobalPrefix('api/v1', {
    exclude: [{ path: '/', method: RequestMethod.GET }],
  });

  app.use(helmet());

  app.useGlobalPipes(
    new ValidationPipe({
      transform: true,
      whitelist: true,
    }),
  );

  const reflector = app.get(Reflector);
  app.useGlobalInterceptors(new ResponseTransformInterceptor(reflector));

  //const rolePermissionsService = app.get(RolePermissionsSeederService);

  //await Promise.all([rolePermissionsService.insertRolePermissions()]);

  const rolePermissionsService = app.get(RolePermissionsSeederService);
  const seedsService = app.get(SeedService);

  const config = new DocumentBuilder()
    .setTitle('Backend api')
    .setDescription('Swagger docs for backend apis')
    .setVersion('1.0')
    .addBearerAuth()
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('/api-docs', app, document);

  await app.init();

  const expressApp = app.getHttpAdapter().getInstance();
  return serverlessExpress({
    app: expressApp,
  });
}

export const handler: Handler = async (
  event: any,
  context: Context,
  callback: Callback,
) => {
  try {
    server = server ?? (await bootstrap());
    return server(event, context, callback);
  } catch (error) {
    console.error('Error during Lambda execution:', error);
    return {
      statusCode: 500,
      body: JSON.stringify({
        error: 'Internal Server Error',
        message: error.message,
      }),
    };
  }
};

Enter fullscreen mode Exit fullscreen mode

Create Dockerfile inside root directory :

# Use the AWS Lambda base image for Node.js
FROM public.ecr.aws/lambda/nodejs:22

# Set the working directory in the container
WORKDIR ${LAMBDA_TASK_ROOT}

# Copy only package files first for dependency installation
COPY package*.json ${LAMBDA_TASK_ROOT}/

# Install dependencies (both production and development for the build phase)
RUN npm install

# Copy the rest of the application code (excluding files in .dockerignore)
COPY . ${LAMBDA_TASK_ROOT}/

# Build the NestJS application
RUN npm run build

# Set the Lambda function handler
CMD ["dist/src/serverless.handler"]
Enter fullscreen mode Exit fullscreen mode

Create .env.dev in project root

NODE_ENV=development
PORT=8035
DATABASE_URL=postgresql://postgres:postgres@database_container:5432/nestjs
REDIS_HOST=redis_container
REDIS_PORT=6379
JWT_TOKEN_SECRET=dkfjkjdfkjdfkdfjkjdfkjkdkdfjk
AWS_BUCKET_NAME=store-backend
API_BACKEND_AWS_REGION=us-east-2
API_BACKEND_AWS_ACCESS_KEY=JKDIUENDIUEKNIUEIJKIWJKWJOUIW
API_BACKEND_AWS_SECRETE_KEY=LKFLKORJKIJKUFKJFKJKFJKFKFJ
AWS_CLOUD_FRONT_URL=https://your.cloudfront.net
AWS_SMTP_HOST=email-smtp.ap-southeast-1.amazonaws.com
AWS_SMTP_USER=AJKDIUENSDUJEHUSDJBUE
AWS_SMTP_PASS=fkdjkjrtiu93j934jkjf98493ikjkjrjii4ju5
AWS_FROM_EMAIL=alamin.cse15@gmail.com
AI_BACKEND_URL=https://aibackend.io/api/v2
CORS_ALLOWED_HOSTS='http://localhost:3000'
FRONTEND_BASE_URL="http://localhost:3000"

MAIL_HOST=smtp.gmail.com
MAIL_USER=alamin.cse15@gmail.com
MAIL_PASSWORD='sdjkdfjkdfkdfhjdfhdjfhj'
MAIL_FROM_NAME=Shaikh
MAIL_FROM_ADDRESS=alamin.cse15@gmail.com
Enter fullscreen mode Exit fullscreen mode

*Install serverless framework globally: *

npm i -g serverless@3.39.0
Enter fullscreen mode Exit fullscreen mode

Setup AWS access and secret key using AWS CLI (setup in ubuntu):

**
Now navigate to project root and run **

sls deploy --stage dev
Enter fullscreen mode Exit fullscreen mode

Make sure you have .env.dev exists in your project root:

**CI/CD: Deploy from github actions:**
Enter fullscreen mode Exit fullscreen mode
name: Deploy Serverless App to Dev Environment

on:
  push:
    branches:
      - dev
    pull_request:
      types: [closed]
      branches:
        - dev

jobs:
  deploy:
    if: github.event.pull_request.merged == true || github.event_name == 'push'
    runs-on: ubuntu-latest

    steps:
      # 1. Checkout code
      - name: Checkout code
        uses: actions/checkout@v3

      # 3. Configure AWS Credentials
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v3
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-2

      # 4. Setup Node.js
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 20.x

      - name: Clear Serverless Cache
        run: |
          rm -rf .serverless

      # 5. Map environment variables manually
      - name: Set Environment Variables
        run: |
          rm -f .env.dev
          touch .env.dev
          echo "NODE_ENV=${{ secrets.NODE_ENV }}" >> .env.dev
          echo "DATABASE_URL=${{ secrets.DEV_DATABASE_URL }}" >> .env.dev
          echo "JWT_TOKEN_SECRET=${{ secrets.DEV_JWT_TOKEN_SECRET }}" >> .env.dev
          echo "AWS_BUCKET_NAME=${{ secrets.DEV_AWS_BUCKET_NAME }}" >> .env.dev
          echo "BACKEND_AWS_REGION=${{ secrets.DEV_BACKEND_AWS_REGION }}" >> .env.dev
          echo "BACKEND_AWS_ACCESS_KEY=${{ secrets.BACKEND_AWS_ACCESS_KEY }}" >> .env.dev
          echo "BACKEND_AWS_SECRETE_KEY=${{ secrets.DEV_BACKEND_AWS_SECRETE_KEY }}" >> .env.dev
          echo "AWS_CLOUD_FRONT_URL=${{ secrets.DEV_AWS_CLOUD_FRONT_URL }}" >> .env.dev
          echo "AWS_SMTP_HOST=${{ secrets.AWS_SMTP_HOST }}" >> .env.dev
          echo "AWS_SMTP_USER=${{ secrets.AWS_SMTP_USER }}" >> .env.dev
          echo "AWS_SMTP_PASS=${{ secrets.AWS_SMTP_PASS }}" >> .env.dev
          echo "AWS_FROM_EMAIL=${{ secrets.AWS_FROM_EMAIL }}" >> .env.dev
          echo "AI_BACKEND_URL=${{ secrets.DEV_AI_BACKEND_URL }}" >> .env.dev
          echo "CORS_ALLOWED_HOSTS=${{ secrets.DEV_CORS_ALLOWED_HOSTS }}" >> .env.dev
          echo "FRONTEND_BASE_URL=${{ secrets.DEV_FRONTEND_BASE_URL }}" >> .env.dev
          echo "MAIL_HOST=${{ secrets.MAIL_HOST }}" >> .env.dev
          echo "MAIL_USER=${{ secrets.MAIL_USER }}" >> .env.dev
          echo "MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }}" >> .env.dev
          echo "MAIL_FROM_NAME=${{ secrets.MAIL_FROM_NAME }}" >> .env.dev
          echo "MAIL_FROM_ADDRESS=${{ secrets.MAIL_FROM_ADDRESS }}" >> .env.dev


      # 6. Install Serverless Framework v3
      - name: Install Serverless Framework v3
        run: npm i -g serverless@3.39.0

        # 7. Deploy to the specified stage
      - name: Deploy to dev
        run: sls deploy --stage dev
Enter fullscreen mode Exit fullscreen mode

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (1)

Collapse
 
shaikhalamin profile image
Shaikh Al Amin

If someone fails to set up the serverless, I can assist you

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay