Create a Lambda function in NodeJS using AWS CLI

Lambda is used by hundreds of thousands of applications, and as such, it's a very important building block of the serverless ecosystem of AWS. Lambda functions are easy to create, and they can accept arguments as well as can return values.

1. What is a Lambda function?

Lambda is the cornerstone of the serverless stack in AWS architectures. It’s a service that invokes a Lambda function when needed.

Lambda functions are functions that perform some logic just like any other function you write. The difference is that you don’t need to provision a server on an EC2 instance or an ECS container to run the function, because AWS provides and manages the underlying infrastructure. Hence the name serverless.

2. Benefits

Lambda functions are excellent candidates for writing pure functions, therefore a more complex logic can be decoupled into smaller pieces. This way, individual functions are easier to test, and bugs are easier to find.

AWS - if we can say so - forces you to write small function. As opposed to running a server 24/7 it won’t cost you anything to write and just have a lambda function. You are only charged when the function is invoked. Billing is calculated in 100-millisecond chunks, so it makes sense to write smaller lambda functions (see pure functions in the last paragraph), because it’ll run for less time, therefore it costs less.

Lambda supports various languages (or runtime environments), I’ll, of course, use NodeJS.

3. The structure of the lambda function

I’ll be using Node v10 in the following examples. This is the latest version supported by AWS, and thankfully, we can avoid using callbacks.

A well-behaved lambda function looks like this:

exports.handler = async (event, context) => {
  // code here ...
  return something
}

As it can be seen, each file where lambda functions are written to can be considered as a module. We need to export the method (in this case, it’s called handler), and it can accept inputs and can have outputs.

In the remaining section of this post, I’ll create, deploy and synchronously invoke a simple lambda function, and will investigate the content of event and context objects.

4. Pre-requisites

You will need a free AWS account, user credentials for programmatic access to AWS services and the AWS CLI installed.

5. Creating a lambda function

First, lambda functions need an IAM role attached. This role will allow lambda to create CloudWatch log groups, log to CloudWatch and access other AWS services.

5.1. Creating the role

Roles don’t make much sense without policies. Policies allow or deny entities (Lambda, in our case) to access other services of AWS.

If you already have a role for Lambda, you can list them using the following CLI command:

aws iam list-roles

If you don’t have any roles, it’s probably easier to create one in the console.

Go to Roles in the IAM section, click on Create Role, then select AWS Service as the trusted entity, Lambda from the Choose the service that will use this role section, then find the AWSLambdaBasicExecutionRole in the Permissions section (this is the policy, which will be attached to the role). Give the role a name (for example, LambdaBasicExecutionRole), and click Create Role. Note the ARN of the role.

5.2. Write the lambda function code

Let’s write a lambda function.

Create a file called index.js in the project folder, and add the following code:

exports.handler = async (event, context) => {
  return {
    event,
    context,
  }
}

The function doesn’t do anything fancy, it simply logs the event and context objects. The name of the function is handler, and it’s an async function. It means that we can await promises inside if we want to, but we don’t want it now. But, by creating an async function, we can get rid of the callback.

5.3. Upload the function to AWS

First, we need to compress the file that contains the function. Let’s zip it:

zip my-lambda-function index.js

The name of the zip file will be my-lambda-function, and we’ll compress index.js.

There should now also be a zip file called my-lambda-function.zip in the project folder.

Next, we can finally create the lambda function:

aws lambda create-function --function-name my-lambda --runtime nodejs10.x \\
--role arn:aws:iam::<ACCOUNT NUMBER>:role/LambdaBasicExecutionRole \\
--handler index.handler --zip-file fileb://my-lambda-function.zip \\
--region us-west-2

Let’s go over the parameters. The name of the lambda function will be my-lambda, and it will run on Node v10. The name of the function containing the logic is handler, and it’s in the file called index.js, so we’ll have to define it as index.handler. The logic and the file are in the zip file, which is in the same folder (hopefully you didn’t navigate away), and we also specify the region, in this case it can be us-west-2.

If everything goes well, we’ll get a response similar to this:

  "FunctionName": "my-lambda",
  "FunctionArn": "arn:aws:lambda:us-west-2:<ACCOUNT NUMBER>:function:my-lambda",
  "Runtime": "nodejs10.x",
  "Role": "arn:aws:iam::<ACCOUNT NUMBER>:role/LambdaBasicExecutionRole",
  "Handler": "index.handler",
  "CodeSize": 283,
  "Description": "",
  "Timeout": 3,
  "MemorySize": 128,
  "LastModified": "2019-08-02T22:19:36.642+0000",
  "CodeSha256": "0NwWHXCZBzTypjjSha+nuLshfs5mfhjBbX0kaJux9Uc=",
  "Version": "$LATEST",
  "TracingConfig": {
      "Mode": "PassThrough"
  },
  "RevisionId": "1aa9d541-1528-4c5f-a8b1-71edfc1c5168"

So far so good, now let’s invoke the function.

5.4. Invoke the function

First, let’s create some input in a JSON format.

The input to the lambda function will be represented in the event object. If we create an input.json file with the following content:

{
  "name": "James Bond",
  "mission": "critical"
}

the event object will have two properties, name and mission with values of James Bond (of course) and critical, respectively.

Let’s invoke the function, and feed in the input from input.json:

aws lambda invoke --function-name my-lambda --payload fileb://input.json \\
 --region us-west-2 response.json

The input is in the payload parameter. The response.json at the end of the command represents the name of the file we want to write the return value into.

Invoking the function will generate the following response in the console:

{
  "StatusCode": 200,
  "ExecutedVersion": "$LATEST"
}

The 200 status code implies that everything went well. At the same time, a file called response.json (specified as the last parameter of the invocation command) has been generated with the return value from the lambda function.

We returned both the event and context objects. response.json looks something like this:

{
  "event": {
    "name": "James Bond",
    "mission": "critical"
  },
  "context": {
    "callbackWaitsForEmptyEventLoop": true,
    "functionVersion": "$LATEST",
    "functionName": "my-lambda",
    "memoryLimitInMB": "128",
    "logGroupName": "/aws/lambda/my-lambda",
    "logStreamName": "2019/08/02/[$LATEST]7fade4a3dc4b45aaab392e64a9f0a38b",
    "invokedFunctionArn": "arn:aws:lambda:us-west-2:<ACCOUNT NUMBER>:function:my-lambda",
    "awsRequestId": "9d8247c5-d9b7-40b6-a63d-665ecb8e9f03"
  }

As it was stated above, event contains the input (the content of input.json), and context has some meta-like pieces of information, which are probably not used very often. It’s visible that Lambda has created a separate log group (called /aws/lambda/my-lambda) in CloudWatch, and we have received the name of the log stream as well.

Lambda sends information about each invocation to CloudWatch under the name of the defined log group and log stream. We can see when the function started to run (marked with START) and when it finished running (END). The duration and billed duration are also displayed. If you decide to write console.log statements in the body of the lambda function, these logs can be read between the START and END marks in CloudWatch.

The callbackWaitsForEmptyEventLoop property means that the response is sent after the event loop has finished.

context has more properties for mobile applications, which are not visible here.

6. Summary

Lambda functions are very popular and play an important part in the serverless ecosystem.

Node v10 supports async function as lambda handlers, and accept two arguments, event (containing the input to the lambda function) and context (mainly metadata, and function invocation-related information).

Thanks for reading, and see you next time.