How to integrate AWS lamda serverless with Node.js Nest.js
Create Serverless yaml: 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+}' Install below two packages: npm i @codegenie/serverless-express npm i -D @types/aws-lambda Create 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, }), }; } }; Create Dockerfile: # 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 applicati
Create Serverless yaml:
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+}'
Install below two packages:
npm i @codegenie/serverless-express
npm i -D @types/aws-lambda
Create 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,
}),
};
}
};
Create Dockerfile:
# 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"]
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
*Install serverless framework globally: *
npm i -g serverless@3.39.0
Setup AWS access and secret key using AWS CLI:
**
Now navigate to project root and run **
sls deploy --stage dev
Make sure you have .env.dev exists in your project root:
**CI/CD: Deploy from github actions:**
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
What's Your Reaction?