Last updated: Jan 27, 2024
Reading timeยท5 min

To provision Lambda Layers in AWS CDK, we have to use the LayerVersion construct.
We are going to provision a Lambda function that has 2 layers:
Clone the github repository.
Install the dependencies:
cd aws-cdk-lambda-layers npm install && npm install --prefix src/layers/yup-utils/nodejs
npx aws-cdk deploy
The lambda function, we have provisioned uses two layers:
calc layer, which exports a simple helper function.This is the code that provisions the 2 Lambda layers:
import * as lambda from 'aws-cdk-lib/aws-lambda'; import {NodejsFunction} from 'aws-cdk-lib/aws-lambda-nodejs'; import * as cdk from 'aws-cdk-lib'; import * as path from 'path'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ๐ layer we've written const calcLayer = new lambda.LayerVersion(this, 'calc-layer', { compatibleRuntimes: [ lambda.Runtime.NODEJS_16_X, lambda.Runtime.NODEJS_18_X, lambda.Runtime.NODEJS_20_X, ], code: lambda.Code.fromAsset('src/layers/calc'), description: 'multiplies a number by 2', }); // ๐ 3rd party library layer const yupLayer = new lambda.LayerVersion(this, 'yup-layer', { compatibleRuntimes: [ lambda.Runtime.NODEJS_16_X, lambda.Runtime.NODEJS_18_X, lambda.Runtime.NODEJS_20_X, ], code: lambda.Code.fromAsset('src/layers/yup-utils'), description: 'Uses a 3rd party library called yup', }); } }
Let's go over the code snippet:
compatibleRuntimes - the runtimes the layer supports. The Lambda function's
runtime must be one of the Layer's compatibleRuntimes, otherwise an error
is thrown.code - the content of the layer. We've called the Code.fromAsset method
with a path that leads to the layer directory, which has a nodejs directory.
The nodejs part is different for
every Lambda runtime.description - a short description of the layerThe code that provisions the Lambda function which uses layers is as follows.
new NodejsFunction(this, 'my-function', { memorySize: 1024, timeout: cdk.Duration.seconds(5), runtime: lambda.Runtime.NODEJS_18_X, handler: 'main', entry: path.join(__dirname, `/../src/my-lambda/index.ts`), bundling: { minify: false, // ๐ don't bundle `yup` layer // layers are already available in the lambda env externalModules: ['aws-sdk', 'yup'], }, layers: [calcLayer, yupLayer], });
Let's go over the snippet:
runtime is one of the layer's compatibleRuntimes.bundling.externalModules prop - it allows us to specify
packages that shouldn't be bundled with the lambda code. Since layers and
the aws-sdk are already available in the lambda environment, we don't want
to bundle them. In this case, we've excluded the yup and aws-sdk
libraries from being bundled with our lambda code.The folder structure of our local src directory looks as follows.
src/ my-lambda/ index.ts layers/ calc/ nodejs/ calc.ts yup-utils nodejs/ node_modules/ package-lock.json package.json yup-utils.ts
However, when the layers get ported inside the lambda function, they are
placed in the /opt directory. The folder structure inside the lambda will
look like so.
/opt nodejs/ calc.js yup-utils.js
So, the way we would import a helper method from the calc layer looks as
follows.
import {double} from '/opt/nodejs/calc';
Let's take a quick look at the code for our layer functions, starting with the calc layer:
export function double(a: number): number { return a * 2; }
And the code for the yup layer:
export * from 'yup';
Notice that we can just write typescript without having to worry about managing
any Webpack configuration because esbuild handles all the heavy lifting.
Let's look at the code of our Lambda function at src/my-lambda/index.ts.
import {APIGatewayProxyEventV2, APIGatewayProxyResultV2} from 'aws-lambda'; /* eslint-disable import/extensions, import/no-absolute-path */ import {double} from '/opt/nodejs/calc'; /* eslint-disable import/extensions, import/no-absolute-path */ import {number, object, string} from '/opt/nodejs/yup-utils'; // ๐ using yup layer const schema = object().shape({ name: string().required(), age: number().required(), }); export async function main( event: APIGatewayProxyEventV2, ): Promise<APIGatewayProxyResultV2> { console.log(event); await schema.isValid({name: 'Tom', age: 24}); return { // ๐ using calc layer body: JSON.stringify({num: double(15)}), statusCode: 200, }; }
We've made use of both of our layers in the lambda code.
Notice how we import the layers using an absolute path.
/* eslint-disable import/extensions, import/no-absolute-path */ import {double} from '/opt/nodejs/calc'; /* eslint-disable import/extensions, import/no-absolute-path */ import {Asserts, number, object, string} from '/opt/nodejs/yup-utils';
Since the layers are not located under /opt/nodejs on our local path, we end
up getting a lot of linting and typescript warnings.
It's important to mention that in order to please TypeScript, we have to
specify the local paths to the layers in our tsconfig.json file:
{ "baseUrl": "./", "paths": { "/opt/nodejs/yup-utils": ["src/layers/yup-utils/nodejs/yup-utils"], "/opt/nodejs/calc": ["src/layers/calc/nodejs/calc"] } }
Let's test the lambda function and layers with the Lambda console.
After running a test with an empty event object I got the following response:

The test shows that the lambda function successfully invoked our calc layer to
double the integer 15 and it also validated an object using the 3rd party
yup library.
The bundle size of the lambda function is only 943 bytes because we've
specified the 3rd party library yup in the externalModules array when
defining our NodejsFunction.

To delete the stack, you can run the following command:
npx aws-cdk destroy
Being able to write all our lambda, layer and infrastructure code using TypeScript is quite nice.
esbuild to transpile and bundle our code, minify our lambda code, and exclude the layer code by specifying the layer package as an external module.The layers are already available in the Lambda runtime so we don't have to bundle them alongside the lambda code.
The way we have to import the lambda layers by specifying an absolute path is unfortunate.
It leads to many Eslint warnings because the layers are not under the same path on the local file system.
We also have to explicitly install the third-party libraries that our layers use before a deployment.
You can learn more about the related topics by checking out the following tutorials: