Skip to content

How to add a layer to SAM hello-world example

0

I'm getting stuck trying to add a layer to the hello world example for nodejs generated through sam init.

I created a layer in the template file:

SampleLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: SampleLayer
      Description: A shared layer for reusable code
      ContentUri: src/layers/sample-layer
      CompatibleRuntimes:
        - nodejs18.x

My folder for the layer is: src/layers/sample-layer/nodejs/node_modules

inside node_modules I have lodash installed for my example.

My function then imports lodash import * as _ from "lodash";

and I get the error:

PS C:\Dev\test-aws\hello-world-test-sam> sam build
Starting Build use cache
Manifest file is changed (new hash: c62c85bee2ce0f21ee736588d5f97fc5) or dependency folder
(.aws-sam\deps\601e9290-694d-4161-b146-6a7a26d1098a) is missing for (helloFromLambdaFunction), downloading dependencies andcopying/building source
Building codeuri: C:\Dev\test-aws\hello-world-test-sam runtime: nodejs22.x architecture: x86_64 functions:
helloFromLambdaFunction
 Running NodejsNpmEsbuildBuilder:CopySource
 Running NodejsNpmEsbuildBuilder:NpmInstall
 Running NodejsNpmEsbuildBuilder:EsbuildBundle

Build Failed
Error: NodejsNpmEsbuildBuilder:EsbuildBundle - Esbuild Failed: X [ERROR] Could not resolve "lodash"

    src/handlers/hello-from-lambda.js:5:19:
      5 │ import * as _ from "lodash";
        ╵                    ~~~~~~~~

  You can mark the path "lodash" as external to exclude it from the bundle, which will remove this error and leave the unresolved path in the bundle.

I have tried the solution of marking the path as external, but that is not feasible as I have hundreds of functions and many imports.

This is part of an attempt for me to convert my app from something that is uploaded in a bitbucket pipeline with atlassian/aws-sam-deploy to the SAM CLI. Previously, we had all our import paths hard coded to /opt/common/... and just never built the app locally.

Thanks for your help with any suggestions or best practices.

2 Answers
1
  1. First, correct the layer structure. AWS Lambda layers need to follow a specific directory structure:
src/layers/sample-layer/
└── nodejs/
    └── node_modules/
        └── lodash/
  1. Modify your template.yaml to include the layer in your function:
Resources:
  SampleLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: SampleLayer
      Description: A shared layer for reusable code
      ContentUri: src/layers/sample-layer
      CompatibleRuntimes:
        - nodejs18.x

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/handlers/
      Handler: hello-from-lambda.handler
      Runtime: nodejs18.x
      Layers:
        - !Ref SampleLayer    # Add this line to reference the layer
  1. In your function code, modify the import statement:
// Instead of ES modules import
const _ = require('lodash');
  1. Create a esbuild configuration file (esbuild.config.js) in your project root:
const esbuild = require('esbuild');

esbuild.build({
  bundle: true,
  entryPoints: ['src/handlers/hello-from-lambda.js'],
  external: ['lodash'], // Mark layer dependencies as external
  format: 'cjs',
  platform: 'node',
  target: 'node18',
  outdir: 'dist',
}).catch(() => process.exit(1));
  1. Update your package.json to include the build script:
{
  "scripts": {
    "build": "esbuild --bundle src/handlers/hello-from-lambda.js --platform=node --target=node18 --external:lodash --outdir=dist"
  }
}
  1. Make sure your function's package.json doesn't include lodash as a dependency (it should only be in the layer's package.json).

  2. Build and deploy:

sam build
sam deploy

Additional tips:

  1. For local testing, you can use the SAM CLI's local invoke:
sam local invoke HelloWorldFunction
  1. If you're using TypeScript, make sure to add type definitions:
npm install --save-dev @types/lodash
  1. To verify the layer is properly included, you can check the Lambda console after deployment.

Common issues to check if you're still having problems:

  1. Ensure the layer and function are using compatible Node.js versions
  2. Verify the layer directory structure is exactly as shown above
  3. Make sure the layer's package.json has the correct dependencies
  4. Check that the function's IAM role has permissions to access the layer

If you need to debug the layer locally:

# Build the layer separately
sam build SampleLayer

# Check the built layer contents
ls -la .aws-sam/build/SampleLayer

This approach should work with your existing setup and allow you to use layers effectively with SAM CLI.

AWS
answered a year ago
EXPERT
reviewed a year ago
  • I need to be able to use ES modules. Assuming those work, does this mean I have to keep a list of externals for all my functions? I have 50+ functions with hundreds of imports so that doesn't seem sustainable.

0

To add a layer to your SAM hello-world example for Node.js and resolve the issue you're facing, you need to make a few adjustments to your configuration and structure. Here's how you can modify your setup:

  1. In your SAM template, make sure you're referencing the layer in your function definition. Add the Layers property to your function:
HelloWorldFunction:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: hello-world/
    Handler: app.lambdaHandler
    Runtime: nodejs18.x
    Layers:
      - !Ref SampleLayer
  1. Ensure your layer structure is correct. For Node.js, the structure should be:
src/layers/sample-layer/nodejs/

Place your node_modules folder directly under this nodejs folder.

  1. In your function code, instead of using ES6 import syntax, use CommonJS require:
const _ = require('lodash');
  1. When building your SAM application, use the --use-container flag to ensure the build process uses a Docker container that mimics the Lambda environment:
sam build --use-container

This approach ensures that your layers are properly included and accessible to your Lambda function during the build process.

  1. If you're still encountering issues, you might need to adjust your esbuild configuration. You can do this by adding an esbuild.config.js file in your function's directory with the following content:
const esbuild = require('esbuild');

esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  outfile: 'dist/app.js',
  external: ['aws-sdk', '/opt/*'],
  platform: 'node',
  target: 'node18',
}).catch(() => process.exit(1));

This configuration tells esbuild to treat anything in /opt (where Lambda layers are mounted) as external.

By following these steps, you should be able to successfully add and use layers in your SAM application. Remember that when deployed, Lambda will automatically make the layer contents available in the /opt directory, so your code will be able to access the dependencies without needing to change import paths.
Sources
Increase efficiency using Lambda layers with AWS SAM - AWS Serverless Application Model
Working with layers for Node.js Lambda functions - AWS Lambda
Building Lambda layers in AWS SAM - AWS Serverless Application Model
Using AWS SAM with layers - AWS Lambda

answered a year ago
EXPERT
reviewed a year ago
  • Unfortunately this AI response is not helpful.

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.