A lambda invoking a lambda. Locally!

Network switch

We all know the joke about ‘serverless’, right? “It’s just someone else’s server.” Yes, it is. But what if I told you it’s possible to run your lambdas not even on someone else’s server? Wait, what?! How does that work?!

Let’s first agree that a desktop computer or laptop is not a server, okay? You probably see where this is going. In stead of running lambdas on Amazon’s server, you can run them locally. Now, this is nothing new, because there is an npm package which allows you to do that: node-lambda. Install it like this (along with aws-sdk): npm i aws-sdk node-lambda -D and add some lines to your package.json:

"scripts": {
"start": "node index.js",
"dry-run": "node-lambda run",
"deploy": "node-lambda deploy --configFile deploy.env",
"setup": "node-lambda setup",
"package": "node-lambda package",
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint .",
"pretest": "npm run lint --silent"
},

Create a context.json, which just needs to contain {} and you’re good to go. Simply type npm run dry-run to invoke your lambda locally. Use a special file .env for adding environment variables for your lambda to use.

But this isn’t the interesting part. The interesting part is chaining lambda invocations. Say you want to invoke a lambda from within another lambda. The code to do this is fairly straightforward:

const AWS = require('aws-sdk');

const lambda = new AWS.Lambda({ region: 'us-east-1' });

module.exports.handler = (event, context, callback) => {
const params = {
FunctionName: 'some-function-name',
Payload: JSON.stringify({ msg: 'Hello, world!' }),
};

lambda.invoke(params, (err, res) => {
console.log(err ? err : JSON.parse(res.Payload).message);

return callback(null, { message: 'Your function executed successfully!' });
})
};

Now, even if you run this code locally using npm run dry-run, this invoke call will try to start the request function in AWS! Now, this is fine most of the time, but not if you’re developing your app on a plane without WiFi (most of them). How useful would it be to be able to invoke a secondary lambda locally?

Luckily, more people have come across this problem, and a few have come up with a solution based on Docker (it’s 2017 after all). The package is called serverless-plugin-simulate and it is, as the name suggests, a plugin for the serverless framework. (See one of my other posts about serverless — the framework.)

Install it like so: npm i serverless-plugin-simulate -D. At the time of writing, the most recent version was 0.0.17. Then, add the following lines to the serverless.yml of the service you wish to invoke:

plugins:
- serverless-plugin-simulate

The tricky part is installing Docker. I run Ubuntu 16.04 on my laptop, so I used this excellent article to install Docker: https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-16-04.

It is wise to install the Docker image(s) the plugin needs, because otherwise it will try to download them on the fly, and this takes a long time (especially on Australian internet speeds). I use node6.10 for my lambdas, so I installed the Docker image that goes with it:

$ docker pull lambci/lambda:nodejs6.10

Once you have all this sorted out, you can start the simulator. Go into the service that you want to simulate locally, and type the following command:

sls simulate lambda -p 4000
Serverless: Starting registry with db at /home/sander/mazzlo/serverless/simulation/.sls-simulate-registry
Serverless: Starting registry at: http://192.168.1.102:4000

My lambda is actually called ‘simulate’, but that’s a bit of a coincidence.

Next, you’ll need to tell the calling lambda that it should not invoke the lambda in AWS, but rather locally. In the file .env, which I mentioned earlier, add the following lines:

SERVERLESS_SIMULATE=1
SERVERLESS_SIMULATE_LAMBDA_ENDPOINT=http://localhost:4000

Add or change the following lines to make sure the calling function invokes your local lambda:

const endpoint = process.env.SERVERLESS_SIMULATE
? process.env.SERVERLESS_SIMULATE_LAMBDA_ENDPOINT
: undefined;

const lambda = new AWS.Lambda({ region: 'us-east-1', endpoint });

Now, finally, invoke your function using npm run dry-run, and see your secondary lambda being invoked by the primary!

That’s all. You can now develop your lambdas locally. It’s truly serverless, not even running on someone else’s server, nor your own!

The plugin also supports API Gateway, so it’s suitable if you want to go down that path. Maybe I’ll cover that some other time. Happy coding!


You can find some example code here: https://github.com/sandyman/solid-succotash.That will save you from a lot of typing.