For this practice, we will launch a basic app into an AWS Lambda function. The idea is to leverage the nest.js router instead of manually creating each endpoint/method for the Lambda definition. The main components are as follows:
Step 1: Create and set up a Nest.js app with Lambda config
By running the following command you will create the app, or you could an existing one.
nest new my-awesome-project
The next step is to generate the CRUD endpoints in src/app.controller.ts
so you can test the verbs out.
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('/')
getHelloApi(): string {
return 'Hello from Lambda';
}
@Post('/post')
async post(@Body() body: any) {
return body;
}
@Put('/put')
async put(@Body() body: any) {
return body;
}
@Delete('/delete')
async delete() {
return 'deleted resource!';
}
}
Unlike the default Nest src/main.ts
file, the Serverless framework requires you to build a main handler differently. For Lambda to load and run the app, you need to create a file like this: src/lambda.ts
.
// lambda.ts
import { Handler, Context } from 'aws-lambda';
import { Server } from 'http';
import { createServer, proxy } from 'aws-serverless-express';
import { eventContext } from 'aws-serverless-express/middleware';
import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const express = require('express');
// NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this is likely
// due to a compressed response (e.g. gzip) which has not been handled correctly
// by aws-serverless-express and/or API Gateway. Add the necessary MIME types to
// binaryMimeTypes below
const binaryMimeTypes: string[] = [];
let cachedServer: Server;
async function bootstrapServer(): Promise<Server> {
if (!cachedServer) {
const expressApp = express();
const nestApp = await NestFactory.create(
AppModule,
new ExpressAdapter(expressApp),
);
nestApp.use(eventContext());
nestApp.setGlobalPrefix('api');
await nestApp.init();
cachedServer = createServer(expressApp, undefined, binaryMimeTypes);
}
return cachedServer;
}
export const handler: Handler = async (event: any, context: Context) => {
cachedServer = await bootstrapServer();
return proxy(cachedServer, event, context, 'PROMISE').promise;
};
And of course, install the required dependencies:
npm i @nestjs/platform-express
npm i aws-lambda
npm i aws-serverless-express
Step 2: Serverless definition
When working with Serverless framework we need to make yml definition files for deployments. The same definition can run locally for testing purposes.
Install serverless npm package if needed
npm i serverless -g
You might need to login, do it with this command
serverless login
Once logged in all the credentials will setup in your host
Now that serverless is ready, we create our definition file serverless.yml
in app's root:
service: my-service
plugins:
- serverless-plugin-optimize
- serverless-offline
- serverless-jetpack
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
memorySize: 2048
timeout: 20
stage: "dev" #prefix "dev" if undefined
environment:
MY_ENV_VAR: 'MY VALUE'
functions:
main: # The name of the lambda function
# The module 'handler' is exported in the file 'src/lambda'
handler: dist/lambda.handler
description: Nestjs backend
events:
- http:
path: /api/{proxy+}
method: any
cors: true
The important stuff here are the following attributes:
events.http: this refers to the REST capacity for the API. The event "httpApi" has different capacities. AWS Official Docs comparison here.
events.http.path: "/api" prefix is placed, then "{proxy+}" definition will allow us to route requests using Nest app router instead of function's events paths
The provider section contains the technical aspect of the "server". Official docs here.
There are some dev dependencies we need that works as plugins. Two of them for optimization and compression and one for offline testing.
npm i -D serverless-plugin-optimize
npm i -D serverless-offline
npm i -D serverless-jetpack
Step 3: Offline (local) start
After all these steps you are ready to run your app. I added the next line for simplicity to package.json
"local": "npm run build && sls offline start"
After run it you'll face a response like this:
Now you can visit the local url: http://localhost:3000/dev/api/ (prefix dev and api) are set in serverless.yml file and api is a global prefix set in our nest.js app. These prefixes can be modified.
Step 4: Deploy function to AWS Lambda
Before you can deploy the function, you must set up AWS credentials with the minimum required permissions for Lambda, API Gateway, and CloudFormation. If you haven't done this yet, you can follow this guide.
Finally, we can proceed with the deployment. You can run the following commands together or one by one.
"deploy": "npm run build && sls deploy"
Up to this point you should be able to get an endpoint from API Gateway using the specified stage like the following:
Testing time
Now you can hit your methods and verbs by using the provided base endpoint; in case you follow the template the urls would be:
GET: https://<gateway_api_id>.execute-api.us-east-1.amazonaws.com/dev/api/
POST: https://<gateway_api_id>.execute-api.us-east-1.amazonaws.com/dev/api/post
PUT: https://<gateway_api_id>.execute-api.us-east-1.amazonaws.com/dev/api/put
DELETE: https://<gateway_api_id>.execute-api.us-east-1.amazonaws.com/dev/api/delete
You've done it! You now have a basic tinker app. It's up to you to keep exploring Nest features like DTO implementation, validation, and more. In the next article, we'll cover authentication and other key features when working with Lambdas.