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:
./node_modules/.bin/esbuild utils.ts --bundle --platform=node --target=node22 --outfile=$(ARTIFACTS_DIR)/nodejs/index.js
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") })
};
};
🌟 BONUS: Using Powertools Logger as a Global Layer
If you want structured logging, tracing, and metrics — use AWS Lambda Powertools for TypeScript as a global layer. AWS provides it as a public layer:
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs18.x
Layers:
- !Sub
arn:aws:lambda:${AWS::Region}:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:24
Metadata:
BuildMethod: esbuild
BuildProperties:
Minify: true
Target: "es2020"
Sourcemap: false
EntryPoints:
- index.ts
External:
- "@aws-lambda-powertools/*"
- "@aws-sdk/*"
or globally:
Globals:
Function:
Layers:
- !Sub arn:aws:lambda:${AWS::Region}:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:24
Example Use
import { Logger } from "@aws-lambda-powertools/logger";
const logger = new Logger({ serviceName: "MyApp" });
export const handler = async () => {
logger.info("Lambda invoked");
return {
statusCode: 200,
body: JSON.stringify({ message: "Logged with Powertools" })
};
};
Conclusion
You now know how to:
Create a reusable Lambda Layer with esbuild
Structure a Makefile build process for SAM
Use AWS Lambda Powertools for structured logging