Cloud Functions for Node.js using Express.js (Connect) APIs

Chris Bailey
Appsody
Published in
6 min readAug 12, 2019

--

According to a number of surveys that look at what Cloud Functions (eg. AWS Lambda, Azure Functions, Google Cloud Functions) are being used for, the number one use case is REST APIs.

The 2018 Serverless Survey from The New Stack showed that 73% of respondents were building “HTTP REST APIs and web applications”:

The New Stack Serverless Survey 2018: 73% of respondents building REST APIs

Whilst the use of Cloud Functions provides a number of advantages, including being able to rapidly build and deploy code to clouds without having to deal with building, deploying, configuring and scaling servers, one of its challenges is that the APIs that you have to code to lack the domain specific focus and rich library ecosystem that you get from web and microservice frameworks.

Below is an example of an AWS Lambda Function Handler in Node.js:

exports.handler = async function(event, context) {
console.log(“EVENT: \n” + JSON.stringify(event, null, 2))
return context.logStreamName
}

This calls the user provided handler function with two parameters:

  • event which contain field and data from the invoker, which is dependent on on how the function was invoked.
  • context which which additional information about the invocation, function, and execution environment.

In the case of a handler function invoked by a HTTP REST request, the event parameter object contains the following (taken from the AWS Lambda Docs):

{     
"requestContext": {
"elb": {
"targetGroupArn": "arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a"
}
},
"httpMethod": "GET",
"path": "/lambda",
"queryStringParameters": {
"query": "1234ABCD"
},
"headers": {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"accept-encoding": "gzip",
"accept-language": "en-US,en;q=0.9",
"connection": "keep-alive",
"host": "lambda-alb-123578498.us-east-2.elb.amazonaws.com",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
"x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476",
"x-forwarded-for": "72.12.164.125",
"x-forwarded-port": "80",
"x-forwarded-proto": "http",
"x-imforwards": "20"
},
"body": "",
"isBase64Encoded": false
}

Whilst this provides all of the data you might need from the HTTP request, there are no parsers, data validators or other helper APIs provided for working with the request. Additionally, there are no APIs provided to facilitate you to build a valid HTTP response for the request.

By contrast, when you work with web and microservice frameworks for Node.js, such as Express.js, you both get access to those domain specific data types and APIs for working with requests and responses, and access to a rich module system that work with them: there are currently 2,167 modules that depend on the “Connect” request handler and middleware format used by Express.js:

Of course, this is because of a trade-off. When writing code using Cloud Functions you have the benefit of flexibility — the handler function API and datatypes are “generic” because you are able to register your function to work with any type of invoker — whether that’s a HTTP REST request, or something else entirely.

However if you are specifically building a REST API, wouldn’t it be great if you had access to all of the power and domain specific capabilities that you get from those domain specific frameworks?

Node.js Functions with Appsody

Appsody, which provides development tools for creating cloud packaged, cloud native and cloud functions based applications, has recently added an experimentalnodejs-function stack, which makes it possible to do function-style development using the “Connect” APIs used by Express.js.

When combined with Appsody’s ability to build best-practise container images and then deploy in a serverless fashion using Knative, this means you can build and deploy serverless Cloud Function, using domain specific APIs and libraries for building REST APIs.

Getting Started

To get started, you first need to install the “appsody” command line tool (CLI) using the instructions provided.

Note that you need at least version 0.3.0 of the CLI:

$ appsody version
appsody 0.3.0

Next, you need to configure the Appsody CLI to additionally use the “experimental” repository using appsody repo add:

appsody repo add experimental https://github.com/appsody/stacks/releases/latest/download/experimental-index.yaml

This means that using appsody list will now show stacks from both the default “appsodyhub” repo and from the repo of “experimental” stacks:

REPO         ID                VERSION DESCRIPTION
appsodyhub swift 0.1.0 Runtime for Swift applications
appsodyhub java-microprofile 0.2.4 Eclipse MicroProfile using OpenJ9 and Maven
appsodyhub java-spring-boot2 0.3.2 Spring Boot using OpenJ9 and Maven
appsodyhub nodejs-express 0.2.2 Express web framework for Node.js
appsodyhub nodejs 0.2.2 Runtime for Node.js applications
experimental nodejs-functions 0.1.0 Serverless runtime for Node.js functions
experimental quarkus 0.1.0 Quarkus runtime for running Java applications

Now create a new directory for your Node.js Functions project:

mkdir function
cd function

Finally, create your new Node.js Functions project:

appsody init experimental/nodejs-functions

You now have a skeleton project that you can use to develop, test, debug, build and deploy your functions using the Express.js APIs.

Exploring the project

The skeleton project provides the following files:

./.vscode/launch.json
./.vscode/tasks.json
./.appsody-config.yaml
./package.json
./function.js

The purposes of those files are as follows:

  • .vscode/launch.json provides a VSCode configuration for attaching a debugger to the functions project.
  • .vscode/tasks.json provides VSCode configuration so that the Appsody CLI commands can easily run as VSCode Tasks.
  • .appsody-config.yaml provides control over which semantic version(s) of the nodejs-functions stack project works with.
  • package.json the standard file for describing Node.js project and their dependencies.
  • function.js an example Node.js Function

Below is the function.js file in detail, which provides a simple example that responses to GET requests on / with Hello from Appsody!:

module.exports.url = '/'
module.exports.get = function(req, res, next) {
res.send('Hello from Appsody!')
}

Node.js Functions supports the use of multiple functions, and has the following requirements:

  • Each function must be defined in its own .js file
  • Each function file must export aurl field, which states the URL path that it responds on.
  • Each function file must export a Express.js (Connect) function handler, using the HTTP verb it reponds to as the export name.

Beyond those requirements, you can develop the Node.js Functions as you would for any Node.js app, including registering additional module dependencies in the package.json file and then requiring those into your functions.

Running, Testing and Debugging the project

Now that you have created the project, you can utilize the iterative, containerized development environments that Appsody provides to develop your application.

First, see the functions project running by opening your project in VSCode and selecting Terminal > Run Task… > Appsody: run or by using appsody run in the Terminal or Console:

appsody run 

This creates a containerized iterative development, and exposes your Node.js Funtions project to HTTP requests.

Open your browser to: http://localhost:3000

This will display the following:

The Node.js Functions Project running on localhost:3000

Without stopping appsody run, you can now make changes to your project and see those changes immediate reflected.

Rather than showing this by editing the existing function, create a new file called hello.js. In the new file, add the following code:

module.exports.url = '/hello/:id'
module.exports.get = function(req, res, next) {
var id = req.params.id
res.send('Hello ' + id + ' from Appsody!')
}

This is standard Express.js code, that takes a URL parameter which is calls id, and echo’s that back inside the Hello from Appsody! message. Don’t forget to save the file!

Open your browser to: http://localhost:3000/hello/Jane

This will display the following:

Node.js Functions project on localhost:3000/hello/Jane

This has automatically detected the additional function and added it to the iterative development environment.

Just like the Appsody Stacks for Express.js, the running application also has a number of built-in cloud native capabilities, such as:

You can also use the other Appsody CLI commands to test, debug, build and deploy your functions project.

Next Steps

This article covered how to build Node.js Functions using the nodejs-express Appsody Stack which automatically provides the application with cloud-native capabilities such as liveness and readiness checks, along with metrics and observability.

For can read more about debugging the the performance dashboard in “Package your Node.js app for Cloud with Appsody”, and more about the metrics capabilities in “Make your Express.js app Cloud-Native with Appsody”.

For more information on Appsody, join us on Slack, follow us on Twitter and star us on GitHub.

--

--

Chris Bailey
Appsody

Distinguished Engineer and Technical Executive at IBM, leading development of Observability (Instana) and AIOps products.