# AWS Lambda Bundle Size Optimization Guide ## Overview This guide documents how to optimize AWS Lambda function bundle sizes when using: - **Serverless Framework v4** (with built-in esbuild) - **Prisma ORM** (with driver adapters) - **NestJS** or any Node.js framework ## Problem Lambda functions can become bloated (25+ MB) due to: 1. **Prisma binary engines** (~50MB uncompressed) 2. **AWS SDK v3** being bundled (~5-10MB) 3. **Dependencies copied to node_modules** instead of being bundled ## Solution Architecture ``` ┌─────────────────────────────────────────────────────────┐ │ Lambda Function │ │ ┌─────────────────────────────────────────────────┐ │ │ │ Your Code (bundled by esbuild) ~50-500 KB │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ Prisma Layer (shared) ~15 MB │ │ │ │ - @prisma/client │ │ │ │ - @prisma/adapter-pg │ │ │ │ - .prisma/client (generated) │ │ │ │ - pg driver │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ AWS Lambda Runtime │ │ │ │ - AWS SDK v3 (built-in for Node.js 18+) │ │ │ └─────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ ``` --- ## Step-by-Step Setup ### 1. Project Structure ``` your-project/ ├── serverless.yml ├── package.json ├── prisma/ │ └── schema.prisma ├── layers/ │ └── prisma/ │ └── nodejs/ │ └── package.json ├── src/ │ └── ... your code └── build-prisma-layer.ps1 (or .sh for Linux/Mac) ``` ### 2. Prisma Schema Configuration **`prisma/schema.prisma`** ```prisma generator client { provider = "prisma-client-js" // For Prisma 7+ with driver adapters, no binary targets needed // The WASM-based query engine is used automatically } datasource db { provider = "postgresql" url = env("DATABASE_URL") } ``` > **Note**: Prisma 7+ uses WASM-based query compiler instead of binary engines when using driver adapters, which is much smaller. ### 3. Layer Package.json **`layers/prisma/nodejs/package.json`** ```json { "name": "prisma-layer", "version": "1.0.0", "description": "Lambda layer for Prisma with pg driver adapter", "dependencies": { "@prisma/client": "^7.0.0", "@prisma/adapter-pg": "^7.0.0", "pg": "^8.13.0" } } ``` ### 4. Serverless Configuration **`serverless.yml`** ```yaml service: your-service-name provider: name: aws runtime: nodejs22.x region: your-region memorySize: 512 # Apply Prisma layer to ALL functions layers: - !Ref PrismaLambdaLayer environment: DATABASE_URL: ${env:DATABASE_URL} # ... other env vars # esbuild configuration (Serverless v4 built-in) build: esbuild: bundle: true minify: true sourcemap: false target: node20 platform: node # Mark packages as external (not bundled into JS) external: - '@prisma/client' - '@prisma/adapter-pg' - '.prisma/client' - '.prisma' - 'pg' - '@aws-sdk/*' - '@smithy/*' - '@aws-crypto/*' # Exclude from npm install in zip (CRITICAL!) exclude: - 'aws-sdk' - '@aws-sdk/*' - '@smithy/*' - '@aws-crypto/*' - '@prisma/client' - '@prisma/adapter-pg' - '.prisma' - '.prisma/client' - 'pg' - 'pg-*' - 'postgres-*' - 'pgpass' - 'split2' - 'xtend' # Define the Prisma layer layers: prisma: path: layers/prisma name: ${self:service}-prisma-layer-${sls:stage} description: Prisma client with pg driver adapter compatibleRuntimes: - nodejs22.x retain: false # Package configuration package: individually: true patterns: - '!node_modules/**' - '!node_modules/@prisma/**' - '!node_modules/.prisma/**' - '!**/*.test.js' - '!**/*.spec.js' - '!**/test/**' - '!**/__tests__/**' - '!package-lock.json' - '!yarn.lock' - '!README.md' - '!.git/**' functions: myFunction: handler: src/handlers/myHandler.handler events: - httpApi: path: /my-endpoint method: get plugins: - serverless-offline ``` ### 5. Build Script for Prisma Layer **Windows (PowerShell) - `build-prisma-layer.ps1`** ```powershell # Build Prisma Lambda Layer $layerPath = "layers\prisma\nodejs" Write-Host "Building Prisma Lambda Layer..." -ForegroundColor Cyan # 1. Clean existing node_modules in layer Write-Host "Cleaning layer node_modules..." if (Test-Path "$layerPath\node_modules") { Remove-Item -Recurse -Force "$layerPath\node_modules" } # 2. Install dependencies in layer Write-Host "Installing layer dependencies..." Push-Location $layerPath npm install --omit=dev Pop-Location # 3. Generate Prisma client Write-Host "Generating Prisma client..." npx prisma generate # 4. Copy .prisma/client to layer Write-Host "Copying generated Prisma client to layer..." $sourcePrisma = "node_modules\.prisma" $destPrisma = "$layerPath\node_modules\.prisma" if (Test-Path $sourcePrisma) { if (Test-Path $destPrisma) { Remove-Item -Recurse -Force $destPrisma } Copy-Item -Recurse $sourcePrisma $destPrisma Write-Host "Copied .prisma/client successfully!" -ForegroundColor Green } else { Write-Host "ERROR: .prisma folder not found. Run 'npx prisma generate' first." -ForegroundColor Red exit 1 } # 5. Show layer size $layerSize = (Get-ChildItem "$layerPath\node_modules" -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB Write-Host "`nTotal layer size: $([math]::Round($layerSize, 2)) MB" -ForegroundColor Yellow Write-Host "Prisma layer built successfully!" -ForegroundColor Green ``` **Linux/Mac (Bash) - `build-prisma-layer.sh`** ```bash #!/bin/bash set -e LAYER_PATH="layers/prisma/nodejs" echo "Building Prisma Lambda Layer..." # 1. Clean existing node_modules in layer echo "Cleaning layer node_modules..." rm -rf "$LAYER_PATH/node_modules" # 2. Install dependencies in layer echo "Installing layer dependencies..." cd "$LAYER_PATH" npm install --omit=dev cd - # 3. Generate Prisma client echo "Generating Prisma client..." npx prisma generate # 4. Copy .prisma/client to layer echo "Copying generated Prisma client to layer..." if [ -d "node_modules/.prisma" ]; then rm -rf "$LAYER_PATH/node_modules/.prisma" cp -r "node_modules/.prisma" "$LAYER_PATH/node_modules/.prisma" echo "Copied .prisma/client successfully!" else echo "ERROR: .prisma folder not found. Run 'npx prisma generate' first." exit 1 fi # 5. Show layer size LAYER_SIZE=$(du -sm "$LAYER_PATH/node_modules" | cut -f1) echo "Total layer size: ${LAYER_SIZE} MB" echo "Prisma layer built successfully!" ``` ### 6. Prisma Client Usage **`src/common/database/prisma.client.ts`** ```typescript import { PrismaClient } from '@prisma/client'; import { PrismaPg } from '@prisma/adapter-pg'; import { Pool } from 'pg'; // Connection pool for serverless const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 5, // Limit connections in Lambda }); const adapter = new PrismaPg(pool); // Single instance for Lambda warm starts let prisma: PrismaClient; export function getPrismaClient(): PrismaClient { if (!prisma) { prisma = new PrismaClient({ adapter, log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'], }); } return prisma; } export { prisma }; ``` --- ## Deployment Workflow ```bash # 1. Build the Prisma layer (run after schema changes) ./build-prisma-layer.ps1 # Windows # or ./build-prisma-layer.sh # Linux/Mac # 2. Deploy npx serverless deploy --stage=dev # 3. Deploy single function (faster, uses existing layer) npx serverless deploy function -f myFunction --stage=dev ``` --- ## Key Configuration Explained ### esbuild `external` vs `exclude` | Property | Purpose | |----------|---------| | `external` | Tells esbuild NOT to bundle these into the JS file. They become `require()` calls. | | `exclude` | Tells Serverless NOT to `npm install` these packages into the function zip. | **Both are required!** - `external` alone = esbuild doesn't bundle, but Serverless still installs to node_modules - `exclude` alone = Serverless doesn't install, but esbuild bundles the code ### Layer Reference ```yaml # This creates a CloudFormation reference to the layer defined in the same stack layers: - !Ref PrismaLambdaLayer ``` The `PrismaLambdaLayer` name comes from the layer key (`prisma`) converted to PascalCase + `LambdaLayer`. ### Why Exclude pg-* packages? When `pg` is external, its dependencies still get installed: - `pg-connection-string` - `pg-pool` - `pg-protocol` - `pg-types` - etc. These must all be in the `exclude` list to prevent duplication. --- ## Expected Results | Function Type | Before | After | |---------------|--------|-------| | Simple handlers | 25+ MB | **50-100 kB** | | With validation (zod/yup) | 25+ MB | **300-500 kB** | | With S3 uploads | 30+ MB | **1-2 MB** | --- ## Troubleshooting ### 1. "Cannot find module '@prisma/client'" **Cause**: Layer doesn't have the generated `.prisma/client` **Fix**: Run `build-prisma-layer.ps1` to regenerate the layer ### 2. Function size still large **Debug**: Extract and inspect the zip: ```powershell Expand-Archive ".serverless\build\your-function.zip" -DestinationPath "extracted" Get-ChildItem "extracted\node_modules" -Directory ``` If you see `@prisma` or `pg` folders, the `exclude` config isn't working. ### 3. "Cannot resolve CloudFormation reference" **Cause**: Using `${cf:...}` reference before first deploy **Fix**: Use `!Ref PrismaLambdaLayer` instead (works on first deploy) ### 4. Cold starts still slow Consider: - **Provisioned Concurrency**: Pre-warm instances - **Reduce memory**: Sometimes lower memory = same speed, lower cost - **Connection pooling**: Use tools like PgBouncer for RDS --- ## Additional Optimizations ### 1. Remove duplicate validation libraries Pick ONE of: `zod`, `yup`, or `class-validator`. Don't use all three. ### 2. Tree-shake NestJS If not using full NestJS, import only what you need: ```typescript // Instead of import { Controller, Get } from '@nestjs/common'; // For Lambda handlers, you might not need NestJS at all ``` ### 3. Use AWS SDK v3 selectively ```yaml external: - '@aws-sdk/*' # Exclude all ``` Then in code: ```typescript // AWS SDK v3 is available in Lambda runtime (Node.js 18+) import { S3Client } from '@aws-sdk/client-s3'; ``` --- ## Quick Reference ```yaml # Minimal serverless.yml for Prisma + Lambda optimization build: esbuild: bundle: true minify: true external: - '@prisma/client' - '@prisma/adapter-pg' - '.prisma/client' - '.prisma' - 'pg' - '@aws-sdk/*' exclude: - '@prisma/client' - '@prisma/adapter-pg' - '.prisma' - '.prisma/client' - 'pg' - 'pg-*' - 'postgres-*' - 'pgpass' - 'split2' - 'xtend' - '@aws-sdk/*' layers: prisma: path: layers/prisma name: ${self:service}-prisma-${sls:stage} compatibleRuntimes: - nodejs22.x provider: layers: - !Ref PrismaLambdaLayer ``` --- ## Version Compatibility | Tool | Tested Version | |------|----------------| | Serverless Framework | v4.x | | Prisma | v7.x | | Node.js | 20.x, 22.x | | AWS Lambda Runtime | nodejs20.x, nodejs22.x | --- *Last updated: December 2025*