Creating and Using Lambda Layers with AWS SAM and Node.js
As your Lambda functions grow, you’ll want to avoid duplicating code or dependencies across functions. AWS Lambda Layers solve this by letting you package shared code (like libraries or utilities) once and reuse them across multiple Lambdas. In this post, you’ll learn: How to create a custom Lambda Layer with AWS SAM using Node.js How to bundle it with esbuild using a Makefile How to use AWS Lambda Powertools for TypeScript as a global logging layer What is a Lambda Layer? A Lambda Layer is a ZIP archive containing libraries, a runtime, or other dependencies. It’s extracted to the /opt directory in the Lambda runtime, making it perfect for sharing utility code or dependencies across functions. Step 1: Create a Layer in Your SAM Project Let’s create a utility layer (e.g., utils) to share code. Directory structure: my-sam-app/ ├── layers/ │ └── utils/ │ └── utils.js │ └── package.json │ └── tsconfig.json │ └── Makefile Your utils.js could be something like: export const greet = (name: string) => { return `Hello, ${name}!`; }; Step 2: Use a Makefile with esbuild The tsconfig.json: { "compilerOptions": { "strict": true, "target": "es2023", "preserveConstEnums": true, "resolveJsonModule": true, "noEmit": false, "sourceMap": false, "module": "commonjs", "moduleResolution": "node", "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "outDir": "./utils" }, "exclude": ["node_modules", "**/*.test.ts"] } Create a Makefile inside the utils/ folder: build-Utils: npm install npm run build mkdir -p "$(ARTIFACTS_DIR)/nodejs/node_modules" cp package.json package-lock.json "$(ARTIFACTS_DIR)/nodejs/" # for runtime deps npm install --omit=dev --prefix "$(ARTIFACTS_DIR)/nodejs/" # for runtime deps rm "$(ARTIFACTS_DIR)/nodejs/package.json" # for runtime deps cp -r utils "$(ARTIFACTS_DIR)/nodejs/node_modules" SAM will automatically pick this up when you run sam build. Step 3: Define the Layer in template.yml Resources: UtilsLayer: Type: AWS::Serverless::LayerVersion Properties: LayerName: utils-layer Description: Common utility functions ContentUri: layers/utils CompatibleRuntimes: - nodejs22.x RetentionPolicy: Retain Metadata: BuildMethod: makefile Step 3: Attach Layer to Lambda Function Now add it to your Lambda function: Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: Handler: index.handler Runtime: nodejs22.x Layers: - !Ref UtilsLayer Metadata: BuildMethod: esbuild BuildProperties: Minify: true Target: "es2020" Sourcemap: false EntryPoints: - index.ts External: - utils - "@aws-sdk/*" Step 4: Use Layer in Lambda Code In your index.js or index.ts: import { greet } from 'utils'; export const handler = async () => { return { statusCode: 200, body: JSON.stringify({ message: greet("SAM Developer") }) }; };

As your Lambda functions grow, you’ll want to avoid duplicating code or dependencies across functions. AWS Lambda Layers solve this by letting you package shared code (like libraries or utilities) once and reuse them across multiple Lambdas.
In this post, you’ll learn:
How to create a custom Lambda Layer with AWS SAM using Node.js
How to bundle it with esbuild using a Makefile
How to use AWS Lambda Powertools for TypeScript as a global logging layer
What is a Lambda Layer?
A Lambda Layer is a ZIP archive containing libraries, a runtime, or other dependencies. It’s extracted to the /opt
directory in the Lambda runtime, making it perfect for sharing utility code or dependencies across functions.
Step 1: Create a Layer in Your SAM Project
Let’s create a utility layer (e.g., utils) to share code.
Directory structure:
my-sam-app/
├── layers/
│ └── utils/
│ └── utils.js
│ └── package.json
│ └── tsconfig.json
│ └── Makefile
Your utils.js
could be something like:
export const greet = (name: string) => {
return `Hello, ${name}!`;
};
Step 2: Use a Makefile with esbuild
The tsconfig.json
:
{
"compilerOptions": {
"strict": true,
"target": "es2023",
"preserveConstEnums": true,
"resolveJsonModule": true,
"noEmit": false,
"sourceMap": false,
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./utils"
},
"exclude": ["node_modules", "**/*.test.ts"]
}
Create a Makefile
inside the utils/
folder:
build-Utils:
npm install
npm run build
mkdir -p "$(ARTIFACTS_DIR)/nodejs/node_modules"
cp package.json package-lock.json "$(ARTIFACTS_DIR)/nodejs/" # for runtime deps
npm install --omit=dev --prefix "$(ARTIFACTS_DIR)/nodejs/" # for runtime deps
rm "$(ARTIFACTS_DIR)/nodejs/package.json" # for runtime deps
cp -r utils "$(ARTIFACTS_DIR)/nodejs/node_modules"
SAM will automatically pick this up when you run sam build
.
Step 3: Define the Layer in template.yml
Resources:
UtilsLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: utils-layer
Description: Common utility functions
ContentUri: layers/utils
CompatibleRuntimes:
- nodejs22.x
RetentionPolicy: Retain
Metadata:
BuildMethod: makefile
Step 3: Attach Layer to Lambda Function
Now add it to your Lambda function:
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs22.x
Layers:
- !Ref UtilsLayer
Metadata:
BuildMethod: esbuild
BuildProperties:
Minify: true
Target: "es2020"
Sourcemap: false
EntryPoints:
- index.ts
External:
- utils
- "@aws-sdk/*"
Step 4: Use Layer in Lambda Code
In your index.js
or index.ts
:
import { greet } from 'utils';
export const handler = async () => {
return {
statusCode: 200,
body: JSON.stringify({ message: greet("SAM Developer") })
};
};