I've been really enjoying playing with the Serverless framework recently. The FaaS (Function-as-a-Service) model really appeals to me, and I think it's going to be A Big Deal in the future. The Serverless framework makes interacting with AWS Lambda, API GateWay, and CloudFormation really really easy.
Unfortunately, because your code runs on other-people's-computers (aka. The Cloud) the development workflow is still in its early days. As a developer you spend a lot of time waiting for your function to be packaged (with any dependencies it required), and pushed across the network. Waiting is lame, so I wanted to investigate what I could use to speed up the development process.
Comparing Packages
While there's a number of libraries for simulating AWS Lambda locally, I narrowed down the ones I would try with the fantastic NPM Compare tool I only recently discovered. It makes it super quick and easy to look at the high-level metrics of NPM packages (e.g. stars on Github, frequency of updates, number of maintainers, etc); While these metrics aren't the be-all and end-all, they're a good indication of the "health" of a library.
After a bit of research, I decided to give the top two contenders a run: node-lambda and aws-lambda-local
Here's their comparison on NPM Compare for reference.
node-lambda
This package has been out for a couple of years, which given AWS Lambda was introduced in 2014 is impressive - that combined with the number of releases suggests a lot of effort has gone in to it.
I found the interface to node-lamba
a bit unintuitive; If you call the base command with -h
you get a stack trace - it only works properly on the commands like run
, etc. Calling commands like run
without command line options also results in stack traces. Fortunately the project docs are quite decent, but I do like a tool that takes the time to help you use it rather than just taking a dump on your terminal...
The arguments expected by the node-lambda run
command were also strange; Why is specifying the event.json
done with -j
and the context.json
with -x
? Surely -e
and -c
are more intuitive? The long arguments even start with those letters, and they're not taken by other commands (--eventFile
and --contextFile
respectively). Strange.
Once I'd worked out the correct commands and arguments to use, it worked as expected. I like that specifying the handler function matched what's used in serverless.yml
. Here's an example command based on my Serverless directory structure (which I will elaborate on later):
$ node-lambda run -H index.handler -j event.json -x context.json
node-lambda
also has commands related to packaging and deploying Lambda functions, but these weren't really relevant because I have Serverless to take care of that for me.
aws-lambda-local
One of the big selling points of this package is its lack of dependencies. Given a Lambda Function is just another JavaScript function called with a few special parameters, I like the idea of a simple solution.
Here's the same (nested) function call:
$ lambda-local -f index.js -c context.json -e event.json
One of the things I liked about aws-lambda-local
was that the event and context arguments are optional. In the case of the context.json
file/object, if you didn't specify it an automatically generated context object was created for you (with sensible fake data). Oh, and the arguments made much more sense to me...
Unfortunately the tool expects only the handler
function to be exported from the function file (i.e. it assumes the first exported function it finds is your handler, and there's no way to configure this). This meant my practice of exporting my functions (so they can be tested) didn't play nicely. To get it working I had to make sure I exported my handler function first in all my files. Not a big deal, but not something I would've changed otherwise.
Conclusion
For now I'll go with aws-lambda-local because it's lightweight and doesn't duplicate a bunch of Serverless functionality.
I can run my local test from npm
with scripts in my package.json
like this one (assuming aws-lambda-local
is installed globally):
{
...
"scripts": {
"install:invoke": "cd functions/install && lambda-local -f index.js -e event.json -c context.json"
},
...
}