Cloud Functions for Node.js using Express.js (Connect) APIs
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”:
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 a
url
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:
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:
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:
- Health Checks: http://localhost:3000/health
- Prometheus Metrics: http://localhost:3000/metrics
- Performance Dashboard: http://localhost:3000/appmetrics-dash/
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.