Developing Node.js functions

Node.js function template structure

When you create a Node.js function using the kn func CLI, the project directory looks like a typical Node.js project, with the exception of an additional func.yaml configuration file. Both http and event trigger functions have the same template structure:

Template structure
.
├── func.yaml (1)
├── index.js (2)
├── package.json (3)
├── README.md
└── test (4)
    ├── integration.js
    └── unit.js
1 The func.yaml configuration file is used to determine the image name and registry.
2 Your project must contain an index.js file which exports a single function.
3 You can add required dependencies in the package.json configuration file, which can include local JavaScript files.
4 Integration and unit test scripts are provided as part of the function template.

Dependencies

Developers are not restricted to the dependencies provided in the template package.json file. Additional dependencies can be added as they would be in any other Node.js project.

Example of adding npm dependencies
npm install --save opossum

When the project is built for deployment, these dependencies will be included in the created runtime container image.

Invoking a function

When using the kn func CLI to create a function project you can generate a project that responds to CloudEvents, or one that responds to simple HTTP requests. CloudEvents in Knative are transported over HTTP as a POST request, so both function types will listen and respond to incoming HTTP events.

Node.js functions can be invoked with a simple HTTP request. When an incoming request is received, functions are invoked with a context object as the first parameter.

Context objects

Functions are invoked by providing a context object as the first parameter.

Example context object
function processPurchase(context, data)

This object provides access to the incoming HTTP request information, including the HTTP request method, any query strings or headers sent with the request, the HTTP version, and the request body.

Incoming requests that contain a CloudEvent attach the incoming instance of the CloudEvent to the context object, so that it can be accessed using context.cloudevent.

Context object methods

The context object has a single method, cloudEventResponse() that accepts a data value and returns a CloudEvent.

In a Knative system, if a function deployed as a service is invoked by an event broker sending a CloudEvent, the broker will examine the response. If the response is a CloudEvent, this event will then be handled by the broker.

Example
// Expects to receive a CloudEvent with customer data
function processCustomer(context, customer) {
  // process the customer
  const processed = processCustomer(customer);
  return context.cloudEventResponse(customer);
}

CloudEvent data

If the incoming request is a CloudEvent, any data associated with the CloudEvent is extracted from the event and provided as a second parameter. For example, if a CloudEvent is received which contains a JSON string similar to the following in its data property:

{
  "customerId": "0123456",
  "productId": "6543210"
}

When invoked, the second parameter to the function, after the context object, will be a JavaScript object that has customerId and productId properties.

Example signature
function processPurchase(context, data)

The data parameter in this example is a JavaScript object that contains the customerId and productId properties.

Return values

Functions can return any valid JavaScript type or can have no return value. When a function has no return value specified, and no failure is indicated, the caller will receive a 204 No Content response.

Functions may also return a CloudEvent, or a Message object in order to push events into the Knative Eventing system. In this case, the developer is not required to understand or implement the CloudEvent messaging specification. Headers and other relevant information from the returned values are extracted and sent with the response.

Example
function processCustomer(context, customer) {
  // process customer and return a new CloudEvent
  return new CloudEvent({
    source: 'customer.processor',
    type: 'customer.processed'
  })
}

Returning headers

You can set a response header by adding a headers property to the return object. These headers will be extracted and sent with the response to the caller.

Example response header
function processCustomer(context, customer) {
  // process customer and return custom headers
  // the response will be '204 No content'
  return { headers: { customerid: customer.id } };
}

Returning status codes

You can set a status code that is returned to the caller by adding a statusCode property to the return object:

Example status code
function processCustomer(context, customer) {
  // process customer
  if (customer.restricted) {
    return { statusCode: 451 }
  }
}

Status codes can also be set for errors that are created and thrown by the function:

Example error status code
function processCustomer(context, customer) {
  // process customer
  if (customer.restricted) {
    const err = new Error(‘Unavailable for legal reasons’);
    err.statusCode = 451;
    throw err;
  }
}

Testing a Node.js function locally

Node.js functions can be tested locally on your computer. In the default project that is created when you create a function using kn func create, there is a test folder which contains some simple unit and integration tests.

  1. To run these tests locally, you must install the required dependencies:

    $ npm install
  2. Once you have installed the dependencies, run the tests:

    $ npm test

The default test framework for Node.js functions is tape. If you prefer to use another framework, you can remove the tape dependency from the package.json file and install your preferred testing framework.