A better way to manage Node.js on AWS Lambda

Qing Wei Lim
disney-streaming
Published in
4 min readNov 20, 2018

AWS Lambda is a popular serverless offering that allows developers to deploy stateless applications without going through the hassle of managing infrastructure. For more information, please read AWS Serverless.

The official way to deploy a javascript AWS Lambda is to use AWS Cloudformation’s package and deploy api, details are available in the AWS Docs.

A nice thing with this approach is that it allows us to manage our AWS Lambda with Cloudformation templates, which promotes reproducible deployments and traceability among other things.

However, I find this approach undesirable for several reasons:

  1. It produces a large zip file because it zips everything from project root, including the whole node_modules directory. Additionally, you cannot remove node_modules before packaging because that's how the runtime resolves dependencies.
  2. It makes versioning harder because it publishes the zip file with a generated name (eg. bucket_name/c3cb48f377d29b5d6129d776993240c1), which makes it tricky to reference a file by version (ie. myapp-0.0.1.js).
  3. It couples packaging and deployment, decoupling can be done but is not very straightforward as the template used for deployment is produced by the packaging command.

Code size is generally not a problem for a javascript lambda as long as it’s under the limit. However there are some exceptions, for example:

  • Projects with many static assets like images for documentation
  • Projects with multiple entry points for different lambda functions, which can leverage dead-code-elimination before deployment
  • Organizations that publish AWS lambda often: the code size difference can add up quickly on S3!

Solution: Bundle javascript via Webpack

Webpack is a popular software for javascript bundling, it also provides many useful features like transpilation and tree-shaking (dead code elimination). For this article, I am going to focus on bundling.

Install Webpack

Webpack is generally used as a CLI tool. Before we start, we need to install it:

> cd <project_dir> 
> npm install --save-dev webpack
> npm install --save-dev webpack-cli

The command above install Webpack locally to your project, replace --save-dev to --global if you wish to install it globally

Create Webpack config file

Before bundling, we need to tell Webpack the specifics about our project so that it can help us, this is achieved by creating a config file.

Below is a minimal Webpack config that allows us to produce javascript for our AWS Lambda environment.

const path = require('path'); 
module.exports = {
target: "node", // aws lambda run on Node.js
entry: "./src/app.js", // entry point of app
output: {
// umd allows our code to be run by AWS Lambda
libraryTarget: 'umd',
path: path.resolve(__dirname, "lib"),
filename: "bundle.js"
}
};

This is a very simplistic Webpack config file, the most important bit is the value of module.exports.output.libraryTarget, it has to be umd to be able to run on the AWS Lambda environment.

Create bundle file

> webpack ... 
> ls lib/ bundle.js

A bundled javascript file will be created in the target path.

Publish javascript code with version

By using Webpack to bundle our code, we solve the issue of bloated code size.

Another issue with aws cloudformation package is that it uploads a zip file with non-readable name, which makes versioning harder than necessary.

This can be solved by:

  • Zipping and uploading our code to S3 directly with the version as part of the file name
  • Deploying to AWS Lambda using a Cloudformation template with a specific version

This workflow also solves the coupling issue with package and deploy commands.

Publish javascript to S3 with version

Zipping and uploading our bundled code is as simple as:

> zip lib/bundle.js app-0.1.zip 
> aws s3 cp app-<version>.zip s3://my-bucket/my-dir/app-0.1

Deploy AWS Lambda with a specific version

We can control our deployment using a Cloudformation template that takes a version parameter, for example:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Parameters:
LambdaVersion:
Type: String
Resources:
TestFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs6.10
CodeUri:
Bucket: "my-bucket"
Key: !Sub "my-dir/app-${LambdaVersion}"
Environment:
Variables:
S3_BUCKET: bucket-name

Then, you can deploy a specific version of your lambda like so:

> aws cloudformation deploy \ 
--template-file <your_file> \
--stack-name <a_name> \
--parameter-overrides LambdaVersion=0.1

Conclusion

This article outlined a better way to manage AWS Lambda, the key point is to decouple code packaging and code deployment to give greater flexibility.

Many of these improvements can be accomplished by using Serverless Framework, do check it out if your lambda project is complicated. However when things are simple, I recommend having fewer dependencies.

--

--