DEV Community

Shaikh Al Amin
Shaikh Al Amin

Posted on

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

Top comments (1)

Collapse
 
shaikhalamin profile image
Shaikh Al Amin

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