Skip to content

Jesses Software Engineering Blog

Dec 07

Jesse

NodeJS ES7 AWS Lambda

Running Node.js ES7 on Lambda is straightforward with Babel JS. While slightly inconvenient to have to rely on a compiler, some of the ES7 features such as async/await are invaluable to modern JavaScript programming. Along with deploying ES7 applications, comes the need to be able to test Lambda locally as it will not be possible to edit Lambda functions inline. There is a sample ES7 AWS Lambda project shell, to show an example of how this can be done.

Babel Setup

There’s two parts to the Babel setup. The first is being able to compile the code for deployment to Lambda. This requires the following libraries in package.json:

"dependencies": {
    "babel-runtime": "^6.11.6"
},
"devDependencies": {
    "babel-cli": "^6.16.0",
    "babel-core": "^6.17.0",
    "babel-plugin-transform-async-to-generator": "^6.16.0",
    "babel-plugin-transform-class-properties": "^6.16.0",
    "babel-plugin-transform-runtime": "^6.15.0",
    "babel-preset-es2015": "^6.16.0",
    "babel-preset-stage-0": "^6.16.0"
}

Along with a .babelrc file:

{
  "presets": [
    "stage-0",
    "es2015"
  ],
  "plugins": [
    "transform-runtime",
    "transform-async-to-generator",
    "transform-class-properties"
  ]
}

NOTE: There are numerous other plugins available.

Most of the Babel libraries are only required in dev as the compilation happens prior to deployment. The babel-runtime library is the only necessary prod Babel library for running the compiled code in Lambda.

The second part is to be able to run Lambda locally with a mock event. To achieve this, there are a couple other Babel devDependencies required:

...
"babel-polyfill": "^6.9.1",
"babel-register": "^6.9.0",
...

With the polyfill and register parameters, Babel code can be run locally without the need for compilation. The whole dependency list can be seen in the shell project.

Run Lambda Locally

Once all the Babel dependencies have been installed the project is ready to be coded out. Lambda requires a handler function that accepts an event, context, and callback. As an example, I set up a simple Lambda function that could serve as a backend to an API Gateway route. The function simply returns the path and querystring params from the function invocation.

There are a couple abstractions in there, with the Config object for environmental credential loading and a Controller. I wanted to demonstrate how credentials could be handled, by keeping a local config out of source control and having the production credentials be fed by Lambda ENV vars. A sample of what a local config could look like:

module.exports.values = {
  aws: {
    AWS_ACCESS_KEY_ID: ‘afasfafqfqwfqwf',
    AWS_SECRET_ACCESS_KEY: 'qwfqwqfqwfqwfqwf/wqfqw',
  },
  dynamodb: {
    apiVersion: '2012-08-10',
    region: 'us-east-1',
    endpoint: 'http://localhost:8000',
  }
};

The Controller demonstrates how modular, object-oriented code can be achieved in NodeJS with the newer ES features such as the class keyword.

Now that the Lambda is written, we want to be able to run and test it. There are numerous ways of approaching this problem, but I typically go for the simplest solution. I set up a mock directory which houses a test Lambda event and a file calling the Lambda and injecting the test object. The AWS documentation shows what test events contain for various different triggers, or you could simply run a test Lambda through the AWS UI logging out the event object. To run locally:

ENV=local node -r babel-register -r babel-polyfill mock/run.js

The ENV flag will force the local config loading, and the Babel libraries prevent the need for compiling the code. This was also added to the package file for ease of use:

npm run mock

Running displays success and what the return value is:

SUCCESS { data: { path: { var: 'sup' }, querystring: { q: 'yo' } } }

The run.js file in the mock dir could be made more robust, but the idea behind this is to get a Lambda live not build a Lambda testing framework. Another possible architecture would be to setup a local API using Express or Restify and attaching the routes to the Lambda functions. This would allow for a local API to mirror API Gateway.

NOTE: This mock set up will work for any kind of Lambda trigger as long as the event object is correctly mocked.

Deployment

While it’s preferred to handle deployments via a CI tool, it is simple to do locally as well. First step is to compile the code:

# nuke the old source directory
rm -rf dist && mkdir dist

# compile the code
babel index.js --out-dir dist && babel src --out-dir dist/src

# install production dependencies
cp package.json dist/ && cd dist && npm install --production

Next create the artifact for deployment:

zip -r artifact.zip dist/*

And deploy to Lambda:

aws lambda update-function-code --function-name <LAMBDA-FUNCTION-NAME-HERE> --zip-file fileb://artifact.zip --region us-east-1

These steps can be encapsulated into package.json:

"scripts": {
    "build": "npm run build:init && npm run build:js && npm run build:install",
    "build:init": "rm -rf dist && mkdir dist",
    "build:js": "babel index.js --out-dir dist && babel src --out-dir dist/src",
    "build:install": "cp package.json dist/ && cd dist && npm install --production",
    "package": "npm run build && npm run package:pack",
    "package:pack": "zip -r artifact.zip dist/*",
    "test": "mocha -r babel-register -r babel-polyfill --recursive test/",
    "mock": "ENV=local node -r babel-register -r babel-polyfill mock/run.js",
    "deploy": "npm run package && aws lambda update-function-code --function-name  --zip-file fileb://artifact.zip --region us-east-1"
  }

For a quick deployment:

npm run deploy

Conclusion

Hopefully this was helpful in outlining the steps for getting an ES7 app up and running on Lambda. Lambda is a very useful tool in the AWS workflow, and leveraging newer JavaScript functionality will help keep JavaScript projects modular and maintainable.

Blog Powered By Wordpress