TL;DR 📚
- Why is a Lambdalith approach a high ROI choice for many? 💸
- Learn how to deploy a monolithic NestJS app on AWS Lambda using Webpack and AWS CDK. 🚀
The Lambdalith Edge for NestJS on AWS Lambda 🌟
🧠 A Lambdalith is a monolithic architecture approach for serverless applications where a single AWS Lambda function serves the entire API, rather than deploying separate functions for each endpoint.
Opt for a Lambdalith and reap multiple benefits for your NestJS API:
- Faster Rollouts: Quicker deployments and streamlined management, irrespective of your number of routes.
- Minimized Cold Starts: Enhanced performance through more frequent reuse of a single Lambda function.
- Easier Logging: A single point for logs simplifies monitoring and alert setup.
- Full NestJS Benefits: Fully exploit NestJS's rich features and community support.
While a Lambdalith might mean lengthier cold starts and broader control scopes, its efficiency, simplicity, and high return on investment are unmatched.
Monorepo structure 🚧📦: I strongly advice you embrace a monorepo structure, with a package for your API and a package for your infrastructure (CDK).
Setting Up the Infrastructure with AWS CDK 🏗️
AWS CDK transforms infrastructure into code. Kick things off by installing AWS CDK and initiating a TypeScript project with cdk init app --language typescript
.
In the lib/my-stack.ts
file, begin with the core of your setup: the Lambda function.
// LambdaNestStack in stack.ts
const apiNestHandlerFunction = new Function(this, "ApiNestHandler", {
code: Code.fromAsset("api/dist"), // 👈 This is crucial
runtime: Runtime.NODEJS_18_X,
handler: "main.handler",
environment: {}, // 👈 You might need env variables
});
Next up, create a Rest API with a Lambda proxy at its root. This API Gateway acts as the traffic controller, directing all requests to your Lambda-powered NestJS app. All route paths will be directed to your single Lambda. 🗾
const api = new RestApi(this, "Api", {
deploy: true,
defaultMethodOptions: {
apiKeyRequired: true,
},
});
api.root.addProxy({
defaultIntegration: new LambdaIntegration(apiNestHandlerFunction, { proxy: true }),
});
const apiKey = api.addApiKey("ApiKey"); // 👈 to ease your testing
const usagePlan = api.addUsagePlan("UsagePlan", {
name: "UsagePlan",
apiStages: [
{
api,
stage: api.deploymentStage,
},
],
});
usagePlan.addApiKey(apiKey);
In this snippet, Code.fromAsset("api/dist")
is crucial. It points to the location of our bundled NestJS app, ensuring efficient Lambda execution.
Prepping the NestJS App for Lambda 🦁
Start by creating a new NestJS app with nest new api
. Then, install the @nestjs/platform-express
and @vendia/serverless-express
packages.
You now have a classic NestJS app, ready to be adapted for AWS Lambda.
Next to the main.ts
file, create a new lambda.ts
file. This file will be the entry point of our Lambda function.
// lambda.ts
import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import serverlessExpress from '@vendia/serverless-express';
import { Context, Handler } from 'aws-lambda';
import express from 'express';
import { AppModule } from './app.module';
let cachedServer: Handler;
async function bootstrap() {
if (!cachedServer) {
const expressApp = express();
const nestApp = await NestFactory.create(
AppModule,
new ExpressAdapter(expressApp),
);
nestApp.enableCors();
await nestApp.init();
cachedServer = serverlessExpress({ app: expressApp });
}
return cachedServer;
}
const handler = async (event: any, context: Context, callback: any) => {
const server = await bootstrap();
return server(event, context, callback);
};
module.exports.handler = handler;
This code will be executed by AWS Lambda. It creates a NestJS app and adapts it to the AWS Lambda environment. It also ensures that the NestJS app is only created once, improving performance. ⚡
🗒️ Side Note: You could easily setup a single
main.ts
entry point by leveraging env variables to deduce the execution context: Lambda or local
Now, we need to bundle this TypeScript code into a single file...🤓
Packing it Up with Webpack 📦🧙♂️
There are several ways to bundle a NestJS app for AWS Lambda. You could use Lambda Layers, but this is not the most efficient approach. Instead, we'll use Webpack to bundle our NestJS app into a single file, which we'll then deploy with AWS CDK.
Let's start by creating a new webpack.config.js
file in our API package. This file will define our Webpack configuration.
module.exports = function (options, webpack) {
return {
...options,
entry: ['./src/lambda.ts'],
externals: [],
output: {
...options.output,
libraryTarget: 'commonjs2',
},
plugins: [
...options.plugins,
new webpack.IgnorePlugin({
checkResource(resource) {
// Ignoring non-essential modules for Lambda deployment
return lazyImports.includes(resource);
},
}),
],
};
};
This configuration bundles our Lambda entry file (lambda.ts
) and its dependencies, creating a lean and efficient package for AWS Lambda!
Make sure to create a build-lambda script in your package.json
file!
{
"scripts": {
"build-lambda": "nest build --webpack --webpackPath webpack.config.js"
}
}
Deploying the NestJS App: To the Cloud! ☁️
Your NestJS app is now a compact bundle, thanks to Webpack. Deploying? It's as simple as:
-
Build: Run
npm run build-lambda
in your API package. -
Deploy: In your infrastructure package, execute
cdk deploy
.
And like that, your NestJS app ascends to AWS Lambda, primed for action. 💫
Your High-Performance NestJS App now lives on AWS 🚀
Congratulations! You've unlocked the strategy for a potent, scalable, and efficient NestJS app on AWS Lambda, all packaged neatly with Webpack and AWS CDK. 👏👏
Please feel free to comment if anything was unclear or if you found a better way to achieve this result! 💬
Top comments (5)
Great article ! We've also used this pattern and so far it has been working for us.
However as I was watching AWS re:Invent 2023 - Best practices for serverless developers (SVS401), I noticed they states the following
I like the Pragmatic-lambda approach, but they don't really offer any practical solution for it. Surely we don't want to implement our own little mini framework in a single lambda. We are thinking about using different NestJS lambdas for this, so splitting up our LambdaLith into separate lambas / routes.
Was wondering if your have any thoughts on this ?
Thanks again for the write-up !
Never trust AWS people on this, they don't want our apps to be simple and platform-independent.
Lambdalith's approach can help us move to vm/containers anytime we want.
Dividing services based on DDD's bounded context is a great approach, I use that for most backends.
I would add that using a graphql server as a lambda that represents that bounded context/domain works really well in this approach. And the LambdaLith approach to lambda is ideally suited
nice
now how do i actually invoke the api ?
should i call it like before just change the base URL?
i mean if my http call looked like this
{{baseUrlPrd}}/product/hash-tags
and now my aws api gateway metohd look like this
https://3p6eyut9b3.execute-api.eu-central-1.amazonaws.com/prod/lambda-general
how should i call it now ?
thanks
Hello everyone, I've been developing a solution for modular monoliths using Lambda and NestJS. In this project, my main focus has been on optimizing the bundle with esbuild and adhering to the best practices for RDS, such as using RDS proxy for connection pooling. I believe this could be beneficial to many. The project also includes structured services, APIs, Swagger documentation, as well as the execution of migrations and seed data.
github.com/atlas-cli/nestjs-boiler...