Introduction#

GitHub this note shows

  • Deploy javascript function
  • Deploy typescript using esbuild
  • Deploy typescript using NodeJSFunction
  • Deploy typescript using ECR (Dockerfile)
  • Deploy typescript using bundling (Dockerfile)

JavaScript Function#

To deploy a function written in JavaScript, let use normal lambda function in aws cdk. Similar to python, then index.js and libs will be packaged by CDK as a zip file and deploy to lambda service.

new aws_lambda.Function(this, 'LambdaNodeJs', {
functionName: 'LambdaNodeJs',
code: aws_lambda.Code.fromAsset(path.join(__dirname, './../lambda-nodejs/')),
handler: 'index.handler',
runtime: aws_lambda.Runtime.NODEJS_16_X
})

Heres is the handler index.js

import { S3Client } from '@aws-sdk/client-s3'
const client = new S3Client({ region: 'us-east-1' })
export const handler = async (event, context) => {
console.log('Hello lambda', client)
}

And package.json

{
"name": "hello_world",
"version": "1.0.0",
"description": "hello world sample for NodeJS",
"main": "app.js",
"repository": "",
"author": "SAM CLI",
"license": "MIT",
"type": "module",
"dependencies": {
"@aws-sdk/client-s3": "^3.145.0",
"package.json": "^2.0.1"
},
"scripts": {}
}

We have to install dependeinces in side the directory lambda-nodejs

cd lambda-nodejs
npm install package.json .

TypeScript Esbuild#

To deploy a lambda function written in TypeScript, there are two steps

  • Transpile TS into JS using esbuild
  • Deploy the JS code as normal

[!IPORTANT] Esbuild builds index.ts and libs indto a index.js, so please pay attention to how to setup AWS Credentials Provider. For example, node ts-node index.ts can get AWS credentials from profile, but the builded index.js cannot. We need to setup a credentials provider.

Setup a project structure

|--dist
|--index.js
|--index.ts
|--package.json
|--package-lock.json
|--node_modules

First, install the Esbuild

npm install esbuild

Then bundle all code and dependencies into a index.js file

./node_modules/.bin/esbuild index.ts --bundle --minify --sourcemap --platform=node --target=es2020 --outfile=dist/index.js
cd dist && zip -r index.zip index.js

Here is a sample index.ts

import { ListBucketsCommand, S3Client } from '@aws-sdk/client-s3'
import { Handler } from 'aws-lambda'
const listBucket = async () => {
const client = new S3Client({
region: 'us-east-1',
credentials: {
accessKeyId: '',
secretAccessKey: '',
sessionToken: ''
}
})
const response = await client.send(new ListBucketsCommand({}))
console.log(response)
return response
}
export const handler: Handler = async (event, context) => {
const buckets = await listBucket()
console.log(buckets)
console.log('EVENT: \n' + JSON.stringify(event, null, 2))
return context.logStreamName
}

And package.json

{
"devDependencies": {
"@types/aws-lambda": "^8.10.131",
"esbuild": "0.19.11"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.490.0",
"@aws-sdk/credential-providers": "^3.490.0"
}
}

Finally here is the CDK code

new aws_lambda.Function(this, 'LambdaEsbuildDemo', {
functionName: 'LambdaEsbuildDemo',
runtime: aws_lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
timeout: Duration.seconds(30),
memorySize: 1024,
code: aws_lambda.Code.fromAsset(
path.join(__dirname, './../lambda-esbuild/dist/')
),
role: role
})

TypeScript NodeJSFunction#

The NodejsFunction construct creates a Lambda function with automatic transpiling and bundling of TypeScript or Javascript code. This results in smaller Lambda packages that contain only the code and dependencies needed to run the function. It uses esbuild under the hood.

In CDK, let deploy a TS lambda function by using NodeJSFunction

new aws_lambda_nodejs.NodejsFunction(this, 'HelloLambdaTs', {
functionName: 'HelloLambdaTs',
entry: path.join(__dirname, './../lambda-ts/index.ts'),
handler: 'handler',
runtime: aws_lambda.Runtime.NODEJS_18_X,
timeout: Duration.seconds(29),
bundling: {
nodeModules: [
'@aws-sdk/client-bedrock-runtime',
'@types/aws-lambda',
'@aws-sdk/client-s3'
]
},
depsLockFilePath: path.join(__dirname, './../lambda-ts/package-lock.json'),
role: role
})

By default, all node modules referenced in your Lambda code will be bundled by esbuild. Use the nodeModules prop under bundling to specify a list of modules that should not be bundled but instead included in the node_modules folder of the Lambda package. This is useful when working with native dependencies or when esbuild fails to bundle a module.

The modules listed in nodeModules must be present in the package.json's dependencies or installed. The same version will be used for installation. The lock file (yarn.lock, pnpm-lock.yaml or package-lock.json) will be used along with the right installer (yarn, pnpm or npm).

Here is index.ts

import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
const s3Client = new S3Client({
region: 'ap-southeast-1'
})
export const handler = async (event: any) => {
const response = await s3Client.send(
new PutObjectCommand({
Bucket: 'lambda-deploy-demo-17012024',
Key: 'hehe.txt',
Body: 'hello'
})
)
console.log(response)
return {
body: JSON.stringify({ message: 'hello' }),
statusCode: 200
}
}

And package.json

{
"name": "bedrocklambda",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"types": "./index.d.ts",
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "^3.428.0",
"@aws-sdk/client-s3": "^3.465.0",
"@types/aws-lambda": "^8.10.124",
"aws-lambda": "^1.0.7",
"axios": "^1.6.2",
"package.json": "^2.0.1"
}
}

TypeScript ECR#

  • Prepare Dockerfile
  • Should install esbuild which transpile TS into JS

Setup lambda-ecr directory as following

|--Dockerfile
|--index.ts
|--package.json
|--package-lock.json

Let create a Lambda function using ECR

new aws_lambda.Function(this, 'HelloLambdaTSEcr', {
functionName: 'HelloLambdaTSEcr',
timeout: Duration.seconds(18),
role: role,
code: aws_lambda.EcrImageCode.fromAssetImage(
path.join(__dirname, './../lambda-ecr')
),
handler: aws_lambda.Handler.FROM_IMAGE,
runtime: aws_lambda.Runtime.FROM_IMAGE,
memorySize: 512
})

Here is Dockerfile

FROM public.ecr.aws/lambda/nodejs:18 as builder
WORKDIR /usr/app
COPY package.json index.ts ./
RUN npm install
RUN npm run build
FROM public.ecr.aws/lambda/nodejs:18
WORKDIR ${LAMBDA_TASK_ROOT}
COPY --from=builder /usr/app/dist/* ./
CMD ["index.handler"]

TypeScript Bundling#

  • CDK use Docker image to bundle the TS code and libs to index.js
  • Then CDK deploy as normal

Let create a project structure lambda-bundle

|--Dockerfile
|--index.ts
|--package.json

In CDK, let use docker image to bundle the lambda code written in TS

new aws_lambda.Function(this, 'LambdaTSBundling', {
functionName: 'LambdaTSBundling',
handler: 'index.handler',
code: aws_lambda.Code.fromAsset(path.join(__dirname, './../lambda-bundle/'), {
bundling: {
image: aws_lambda.Runtime.NODEJS_18_X.bundlingImage,
command: ['bash', '-c', 'npm install && npm run build'],
user: 'root'
}
}),
memorySize: 512,
timeout: Duration.seconds(15),
runtime: aws_lambda.Runtime.NODEJS_18_X,
architecture: Architecture.X86_64,
role: role
})

Here is content of package.json

{
"name": "lambda-ecr",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "esbuild index.ts --bundle --minify --sourcemap --platform=node --target=es2020 --outfile=/asset-output/index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "^3.490.0",
"@aws-sdk/client-s3": "^3.490.0",
"@types/aws-lambda": "^8.10.131"
},
"devDependencies": {
"@types/aws-lambda": "^8.10.131",
"esbuild": "0.19.11"
}
}

Reference#