惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Schneier on Security
Schneier on Security
T
The Blog of Author Tim Ferriss
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Security Blog
Microsoft Security Blog
I
InfoQ
罗磊的独立博客
小众软件
小众软件
H
Help Net Security
Vercel News
Vercel News
M
MIT News - Artificial intelligence
雷峰网
雷峰网
Stack Overflow Blog
Stack Overflow Blog
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Heimdal Security Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Application and Cybersecurity Blog
Application and Cybersecurity Blog
W
WeLiveSecurity
Google DeepMind News
Google DeepMind News
B
Blog
Forbes - Security
Forbes - Security
T
Troy Hunt's Blog
月光博客
月光博客
The Cloudflare Blog
Last Week in AI
Last Week in AI
J
Java Code Geeks
Jina AI
Jina AI
TaoSecurity Blog
TaoSecurity Blog
Google Online Security Blog
Google Online Security Blog
C
Check Point Blog
PCI Perspectives
PCI Perspectives
SecWiki News
SecWiki News
P
Proofpoint News Feed
Attack and Defense Labs
Attack and Defense Labs
Cisco Talos Blog
Cisco Talos Blog
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The Last Watchdog
The Last Watchdog
F
Full Disclosure
T
Threatpost
NISL@THU
NISL@THU
量子位
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
F
Fortinet All Blogs
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Hugging Face - Blog
Hugging Face - Blog
有赞技术团队
有赞技术团队

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
How to Deploy NestJS to AWS Lambda Using CDK and GitHub Actions
Ajeet Chaula · 2026-05-08 · via DEV Community

Originally published on ajeetchaulagain.com on March 30, 2026.

Deploying a NestJS application to AWS Lambda behind API Gateway sounds straightforward — until you start wiring things together.

Between adapting NestJS to a serverless runtime, configuring API Gateway, and setting up infrastructure with AWS CDK, there are several moving parts that can quickly become messy. While working on a personal side project, I ran into these challenges firsthand — particularly around adapting NestJS to Lambda's execution model.

By the end of this guide, you'll have:

  • A NestJS app adapted for Lambda's execution model using @codegenie/serverless-express
  • Infrastructure defined as code: a Lambda function and HTTP API Gateway, provisioned with AWS CDK
  • A GitHub Actions workflow that builds and deploys your stack automatically on every push to main

Simple architecture diagram of NestJS deployment in AWS Lambda

The complete source code is available on GitHub.

Prerequisites

To follow along, you'll need:

  • An AWS account
  • Basic familiarity with Node.js and NestJS
  • Basic familiarity with AWS services and tools (AWS CLI, IAM, Lambda, API Gateway, CloudFormation)
  • A GitHub account (for setting up a repository and GitHub Actions)

You don't need to be an AWS expert to follow along, but having a rough idea of how Lambda and API Gateway fit together will make things easier.

Set up a new NestJS project

Start by creating a new NestJS application using the Nest CLI:

npm i -g @nestjs/cli
nest new nestjs-serverless-aws-cdk

Enter fullscreen mode Exit fullscreen mode

This scaffolds a standard NestJS project with all the necessary boilerplate.

For a deeper understanding of NestJS concepts and architecture, I highly recommend checking out the NestJS official documentation.

Now, start the application:

cd nestjs-serverless-aws-cdk
npm run start

Enter fullscreen mode Exit fullscreen mode

You should see the application running at http://localhost:3000/. The default port is configured in src/main.ts:

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

Enter fullscreen mode Exit fullscreen mode

The scaffolded project already includes:

  • A controller (app.controller.ts)
  • A service (app.service.ts)
  • A module (app.module.ts)

Since the focus of this guide is deployment, we'll reuse the default endpoint instead of creating new ones.

Adapting NestJS for AWS Lambda

By default, a NestJS application runs on a long-lived HTTP server. It bootstraps once, initializes its dependencies, and continues handling incoming requests.

AWS Lambda, on the other hand, follows an event-driven model where code is executed per request in short-lived, stateless environments (with occasional cold starts).

Because of this difference, NestJS cannot run directly in Lambda without some adaptation. To bridge this gap, we use @codegenie/serverless-express, which allows a NestJS application to run inside AWS Lambda.

Under the hood, it:

  • Converts the incoming Lambda event into an HTTP request
  • Passes it to your NestJS application
  • Transforms the response back into a Lambda-compatible format

In simple terms, it makes Lambda behave like an HTTP server so NestJS can run without major changes.

Now that we understand the need for additional configuration, let's install the required packages:

npm install @codegenie/serverless-express @types/aws-lambda

Enter fullscreen mode Exit fullscreen mode

Note: @types/aws-lambda provides the TypeScript types used in the handler.

Next, create a new file called lambda.ts inside the src/ directory (alongside main.ts). This will act as the entry point for your Lambda function.

// src/lambda.ts
import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import serverlessExpress from '@codegenie/serverless-express';
import {
  APIGatewayProxyEvent,
  APIGatewayProxyResult,
  Context,
} from 'aws-lambda';
import express from 'express';
import { AppModule } from './app.module';

type AsyncHandler = (
  event: APIGatewayProxyEvent,
  context: Context,
) => Promise<APIGatewayProxyResult>;

let serverlessExpressInstance: AsyncHandler | undefined;

async function setup(
  event: APIGatewayProxyEvent,
  context: Context,
): Promise<APIGatewayProxyResult> {
  const expressApp = express();
  const nestApp = await NestFactory.create(
    AppModule,
    new ExpressAdapter(expressApp),
  );

  nestApp.enableCors();
  await nestApp.init();

  serverlessExpressInstance = serverlessExpress({
    app: expressApp,
  }) as unknown as AsyncHandler;

  return serverlessExpressInstance(event, context);
}

export function handler(
  event: APIGatewayProxyEvent,
  context: Context,
): Promise<APIGatewayProxyResult> {
  if (serverlessExpressInstance) {
    return serverlessExpressInstance(event, context);
  }

  return setup(event, context);
}

Enter fullscreen mode Exit fullscreen mode

A few key things worth calling out in the code above:

  • serverlessExpressInstance is declared outside the handler and only populated on the first invocation. On subsequent invocations within the same Lambda container, the if (serverlessExpressInstance) check skips the full NestJS bootstrap and reuses the existing instance. This is the standard pattern for reducing cold start overhead in Lambda.
  • The setup function is where NestJS bootstraps. It creates an Express app, wraps it with the NestJS ExpressAdapter, and passes it to serverlessExpress. The resulting instance is a function that accepts Lambda events and returns Lambda-compatible responses.

This handler follows the standard async/await pattern and works with modern AWS Lambda Node.js runtimes (including Node.js 20 and 24).

The AWS Lambda Node.js 24 runtime dropped support for the callback-style handler (callback(null, response)) — using it will cause the function to hang until timeout. See the AWS Lambda Node.js runtime docs for details.

Setting up infrastructure using AWS CDK

Now that we have the Lambda handler ready to serve our NestJS application, let's set up the infrastructure using AWS CDK. AWS Cloud Development Kit (CDK) is an Infrastructure as Code (IaC) framework that allows you to define AWS resources using programming languages such as TypeScript, Python, Java, and more. Think of it like this: instead of writing raw CloudFormation templates, you write code that is synthesized into CloudFormation, which is then used to deploy resources to AWS.

The high-level CDK flow looks something like this:

👨‍💻 Write CDK Code
        ↓
🧪 cdk synth
        ↓
📄 CloudFormation Template
        ↓
🚀 cdk deploy
        ↓
☁️ AWS Resources

Enter fullscreen mode Exit fullscreen mode

In practice, CDK makes infrastructure easier to reason about and maintain compared to raw templates.

CDK mental model (quick overview)

Before we define our infrastructure, it helps to understand a few core CDK concepts:

  • App → The root container for your CDK application
  • Stack → A unit of deployment (maps to a CloudFormation stack)
  • Construct → Building blocks used to define AWS resources

In this guide, we'll define a single stack that provisions our Lambda function, API Gateway, and related resources.

Initializing CDK in the project

Now that you've understood the basics of AWS CDK, let's initialize it in our project. To keep the infrastructure code separate from the NestJS source code, you'll initialize the CDK project into a separate subdirectory, infra/.

Run the following command from the project root:

mkdir infra && cd infra
npx cdk init app --language typescript

Enter fullscreen mode Exit fullscreen mode

This generates the basic CDK project structure:

.
├── bin/
├── lib/
├── cdk.json
├── package.json
└── tsconfig.json

Enter fullscreen mode Exit fullscreen mode

Where:

  • bin/ contains the entry point of your CDK app
  • lib/ contains the stack definition for your infrastructure
  • cdk.json contains configuration for the CDK CLI

Configuring AWS credentials (step by step)

Before running any AWS CDK commands, you need to configure AWS credentials on your local machine.

This allows AWS to authenticate your requests and perform actions on your behalf — such as deploying CloudFormation stacks and uploading assets.

For example, when you run:

npx cdk bootstrap

Enter fullscreen mode Exit fullscreen mode

Your IAM identity needs permissions to create foundational resources like:

  • CloudFormation stacks
  • S3 buckets (for storing assets)
  • IAM roles and policies

In production environments, it's best to follow the principle of least privilege, granting only the permissions required for a specific task.

However, to keep this guide simple and focused, we'll use an IAM user with the AdministratorAccess policy attached.

Follow these steps to configure AWS credentials:

1. Install AWS CLI

Make sure AWS CLI is installed on your machine. Follow the official installation guide.

2. Create an IAM user

Create an IAM user with the AdministratorAccess policy and generate an access key for it.

  1. Log in to your AWS console
  2. Navigate to IAM → Users → Create User
  3. Create a user (e.g., cdk-bootstrap-admin)
    • Attach the AdministratorAccess policy
    • Generate an access key

Make sure to download the secret key for use later — you won't be able to view it again.

3. Run AWS Configure

Run aws configure to set up credentials for the AWS CLI so it can authenticate your requests:

aws configure

Enter fullscreen mode Exit fullscreen mode

You'll be prompted to enter:

AWS Access Key ID:
AWS Secret Access Key:
Default region name: us-east-1
Default output format: json

Enter fullscreen mode Exit fullscreen mode

Paste the access keys you generated in the previous step.

4. Verify credentials configuration

To confirm AWS credentials are configured correctly, run the following AWS CLI command, which will show the details of your AWS account and IAM user:

aws sts get-caller-identity

Enter fullscreen mode Exit fullscreen mode

Output:

{
  "UserId": "UserId",
  "Account": "AccountNumber",
  "Arn": "arn:aws:iam::<AccountNumber>:user/cdk-bootstrap-admin"
}

Enter fullscreen mode Exit fullscreen mode

Bootstrapping the CDK project

Now that your AWS credentials are configured locally, you're ready to bootstrap your AWS environment for CDK.

Bootstrapping prepares your AWS account so that AWS CDK can deploy resources on your behalf.

Run the following command in your infra/ directory:

npx cdk bootstrap

Enter fullscreen mode Exit fullscreen mode

This command creates a foundational one-time CloudFormation stack (called CDKToolkit) in your AWS account, which sets up required resources such as IAM roles and an S3 bucket to upload CloudFormation templates. These resources are needed for future CDK deployments.

Screenshot listing resources created by bootstrap in the CDKToolkit CloudFormation stack

You only need to run cdk bootstrap once per account and region.

Writing the CDK stack

Now that you've bootstrapped your environment, you're ready to define your infrastructure using AWS CDK. In this section, you'll create a stack that provisions a Lambda function for your application.

I'm also using a Lambda layer to bundle production dependencies. Separating node_modules from the application code helps reduce the size of the deployment package and allows the same layer to be reused across multiple Lambda functions.

Navigate to the infra/lib directory, where you'll find a file named infra-stack.ts. This file was generated during CDK project initialization. You can rename the stack if needed, but for this guide, we'll keep it as is.

Add the following stack definition code to infra/lib/infra-stack.ts:

// infra/lib/infra-stack.ts
import * as cdk from 'aws-cdk-lib/core';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2';
import * as integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
import * as path from 'path';

export class InfraStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const nodeModulesLayer = new lambda.LayerVersion(this, 'NodeModulesLayer', {
      code: lambda.Code.fromAsset(path.join(__dirname, '../../layer')),
      compatibleRuntimes: [lambda.Runtime.NODEJS_24_X],
      description: 'Production node_modules for NestJS Lambda',
    });

    const nestApiLambda = new lambda.Function(this, 'NestApiLambdaFunction', {
      runtime: lambda.Runtime.NODEJS_24_X,
      handler: 'src/lambda.handler',
      code: lambda.Code.fromAsset(path.join(__dirname, '../../dist'), {
        exclude: ['infra', 'tsconfig*'],
      }),
      memorySize: 512,
      layers: [nodeModulesLayer],
    });

    const httpApi = new apigwv2.HttpApi(this, 'HttpApi', {
      defaultIntegration: new integrations.HttpLambdaIntegration(
        'LambdaIntegration',
        nestApiLambda,
      ),
    });

    new cdk.CfnOutput(this, 'HttpApiUrl', {
      value: httpApi.url!,
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

In this setup, you're using a few core CDK constructs to define your infrastructure:

  • lambda.Function
  • lambda.LayerVersion
  • apigwv2.HttpApi

In CDK, constructs are the basic building blocks used to model infrastructure. If you haven't come across constructs before, the official docs are worth a quick read: AWS CDK Constructs.

Now let's go through the different configuration parts in the code above.

Lambda layer configuration

const nodeModulesLayer = new lambda.LayerVersion(this, 'NodeModulesLayer', {
  code: lambda.Code.fromAsset(path.join(__dirname, '../../layer')),
  compatibleRuntimes: [lambda.Runtime.NODEJS_24_X],
  description: 'Production node_modules for NestJS Lambda',
});

Enter fullscreen mode Exit fullscreen mode

This code defines a Lambda layer using the LayerVersion construct, where the code is loaded from a layer/ directory, which doesn't exist yet.

To create it, add the following script to your root package.json:

"build:lambda-layer": "mkdir -p layer/nodejs && cp package.json package-lock.json layer/nodejs/ && npm ci --prefix layer/nodejs --omit=dev && rm layer/nodejs/package.json layer/nodejs/package-lock.json"

Enter fullscreen mode Exit fullscreen mode

Since we don't need dev dependencies for our Lambda runtime, this script installs only production dependencies into layer/nodejs/. In short, it copies the existing package.json and lock file into layer/nodejs/ temporarily and installs production dependencies (npm ci --omit=dev) that our application needs.

Now, run that script from the project root to create a /layer directory (we'll need this before deploying):

npm run build:lambda-layer

Enter fullscreen mode Exit fullscreen mode

Also add /layer/nodejs/node_modules to your .gitignore. This directory should be generated during builds, not committed.

Lambda function configuration

const nestApiLambda = new lambda.Function(this, 'NestApiLambdaFunction', {
  runtime: lambda.Runtime.NODEJS_24_X,
  handler: 'src/lambda.handler',
  code: lambda.Code.fromAsset(path.join(__dirname, '../../dist'), {
    exclude: ['infra', 'tsconfig*'],
  }),
  memorySize: 512,
  layers: [nodeModulesLayer],
});

Enter fullscreen mode Exit fullscreen mode

This code defines our Lambda function along with some configuration. Here's a breakdown of the key settings:

  • runtime: lambda.Runtime.NODEJS_24_X — sets Node.js v24 as the runtime used by the Lambda function
  • handler: 'src/lambda.handler' — the entry point Lambda invokes. We created this handler previously in src/lambda.ts. Since the application is deployed from the dist directory which contains a src folder, the handler is defined as src/lambda.handler.
  • code: lambda.Code.fromAsset(…) — packages the compiled application (dist/) and uploads it as the deployment bundle
  • exclude: […] — skips unnecessary files from the bundle. I would strongly recommend being intentional here — smaller bundles lead to faster deployments. For example, I've excluded the infra directory and tsconfig* related files present in our dist directory. The only files needed for the NestJS runtime are in dist/src.
  • memorySize: 512 — allocates 512 MB of memory to our Lambda runtime. NestJS has a heavier bootstrap compared to minimal handlers, so this tends to give better performance out of the box. Without memorySize, it defaults to 128 MB.
  • layers — attaches the layer defined above. This layer is merged into the function's filesystem at runtime.

HTTP API Gateway configuration

const httpApi = new apigwv2.HttpApi(this, 'HttpApi', {
  defaultIntegration: new integrations.HttpLambdaIntegration(
    'LambdaIntegration',
    nestApiLambda,
  ),
});

Enter fullscreen mode Exit fullscreen mode

Here you define an HTTP API using API Gateway v2. This guide uses HTTP API (v2), which is cheaper than REST API (v1), has lower latency, and is sufficient for NestJS since routing lives inside the application. See the official comparison if you need the advanced features REST API offers.

This uses HttpLambdaIntegration, which enables Lambda proxy integration. In simple terms, any incoming HTTP requests to API Gateway are forwarded to your Lambda as an event, and the response from Lambda is forwarded back to the client unchanged.

By setting this as the default integration, CDK creates a $default route — a catch-all that matches any HTTP method and any path. This is an important consideration for our NestJS application because we want the routing to happen inside the application itself, not at the API Gateway level.

CloudFormation outputs

new cdk.CfnOutput(this, 'HttpApiUrl', {
  value: httpApi.url!,
});

Enter fullscreen mode Exit fullscreen mode

This prints the generated endpoint URL to the terminal after running cdk deploy. Something like this:

Outputs:
InfraStack.HttpApiUrl = https://abc123.execute-api.us-east-1.amazonaws.com/
Stack ARN:
arn:aws:cloudformation:us-east-1:<account-number>:stack/InfraStack/a0fb4130-2a7f-11f1-ab1d-12c493467933

Enter fullscreen mode Exit fullscreen mode

Deploying the infrastructure

At this point, your infrastructure is defined. Now it's time to deploy it.

Deploying locally

Before running the cdk deploy command, let's first validate the CloudFormation stack by running cdk synth from the infra/ directory:

cd infra && npx cdk synth

Enter fullscreen mode Exit fullscreen mode

This compiles your CDK code and generates the CloudFormation template in the infra/cdk.out/ directory. It's a quick way to catch configuration issues before running a full deployment.

CDK synth output in the terminal

Once synthesis succeeds, make sure your tsconfig.build.json excludes the infra/ directory and explicitly sets rootDir:

// tsconfig.build.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "rootDir": "./"
  },
  "exclude": ["node_modules", "test", "dist", "infra", "**/*spec.ts"]
}

Enter fullscreen mode Exit fullscreen mode

Two things are happening here:

  • Excluding infra/ — without this, nest build will try to compile your CDK code as part of the application. Since CDK dependencies like aws-cdk-lib are installed inside infra/node_modules (not at the project root), the build will fail with module resolution errors.
  • Setting rootDir — controls the output folder structure. Without it set explicitly, TypeScript figures it out from whichever files are included. Once infra/ is excluded, TypeScript only sees src/ and uses that as the root — so lambda.ts compiles to dist/lambda.js instead of dist/src/lambda.js, which no longer matches the handler path you defined in the stack.

Now build your application (dist/) and Lambda layer (layer/) from the project root:

npm run build
npm run build:lambda-layer

Enter fullscreen mode Exit fullscreen mode

This ensures your compiled application and production dependencies are ready for deployment.

Now run the deploy command from the infra/ directory:

npx cdk deploy

Enter fullscreen mode Exit fullscreen mode

This starts the CDK deployment process. You'll see a summary of IAM and resource changes before it proceeds.

CDK deploy confirmation prompt showing IAM and resource changes

Confirm the deployment when prompted. After a successful deploy, you'll see the API URL in the terminal output.

CDK deploy output showing the HttpApiUrl

Hit this URL and you should see the "Hello World!" response from your app.controller.ts endpoint — your NestJS app is live on AWS Lambda!

Hello World response from the deployed NestJS Lambda endpoint

Deploying with GitHub Actions

So far, you've been deploying locally using cdk deploy. That works well for development, but in a production-grade setup, you'll want deployments to run through CI.

A common approach is to trigger deployments automatically when changes are pushed to the main branch. In this section, we'll keep things simple and use GitHub Actions to automate deployments on every push to main. I won't walk through the workflow line by line — the goal here is to get a working setup in place.

If you're new to GitHub Actions, the official docs are a good place to start.

Add the following file .github/workflows/deploy.yml:

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4.2.2

      - name: Set up Node.js
        uses: actions/setup-node@v4.4.0
        with:
          node-version: 24
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build application
        run: npm run build

      - name: Build Lambda layer
        run: npm run build:lambda-layer

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4.1.0
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ vars.AWS_REGION }}

      - name: Install CDK dependencies
        run: npm ci
        working-directory: infra

      - name: Deploy CDK Stack
        run: npx cdk deploy --require-approval never
        working-directory: infra

Enter fullscreen mode Exit fullscreen mode

At a high level, this workflow:

  • Installs dependencies
  • Builds the application (dist/) and Lambda layer (/layer)
  • Configures AWS credentials for CI
  • Deploys the CDK stack

AWS credentials for CI

If you look at the workflow code, you'll see it uses GitHub secrets and variables to authenticate with AWS:

  • AWS_ACCESS_KEY_ID — stored as a secret
  • AWS_SECRET_ACCESS_KEY — stored as a secret
  • AWS_REGION — stored as a variable

Make sure you add these in your repository settings. You can follow the official guides:

Note: GitHub masks secret values in logs, so outputs like your API URL (which includes the region) will also be masked and won't be directly clickable. Using vars for AWS_REGION keeps the output readable and ensures the endpoint URL is visible and usable, while sensitive values remain stored in secrets.

Once everything is in place, push your changes (including the workflow file) to the main branch. This will trigger the deployment automatically.

You can monitor the workflow from the Actions tab in your repository.

After the workflow completes, open the workflow run and check the Deploy CDK Stack step — the HttpApiUrl will be printed there without the region being masked.

GitHub Actions deploy workflow run showing the HttpApiUrl output

Conclusion

That's the full setup. If you've made it this far, you have a production-ready baseline for running NestJS on AWS Lambda!

You adapted NestJS to fit Lambda's execution model, defined your infrastructure as code using CDK, exposed it through API Gateway, and automated deployments with GitHub Actions. None of these pieces are complex on their own — but together they give you a setup that's consistent, repeatable, and easy to extend.

A few things this setup gets right out of the box:

  • Infrastructure is version-controlled alongside your application code
  • Deployments are automated and consistent, and can be extended to support multiple environments
  • NestJS runs in Lambda without changes to your routing or application logic — the gateway proxies everything through and NestJS handles it internally

From here, you can layer in things like custom domains, environment-specific stacks, API Gateway authentication, or move to container-based Lambda deployments if your workloads grow.

Thanks for following along — enjoy the serverless!


If this saved you time, you can find more posts like this on my blog or follow me on LinkedIn.