Beware here may be dragons: this is document is in draft and very much liable to change. Don't take anything seriously here.
Click to hide this warning.

This article will describe Node-REDs link nodes. These come if three favours link-in, link-out and link-call nodes. Their usefulness becomes clear when flows become repetitive and complex.

First though I will introduce some Node-RED terminology, if you are already aware of Node-REDs basic functionality, then skip directly to the link nodes description.

I begin with a hello world flow to present the basic Node-RED terminology.

From Code to Shapes

Traditionally coding has involved using a text-based editor to modify lines of code, i.e., instructions that are carried out by a machine. Lines and lines of code make a program that does something useful. A random example of some code:

[...]
    if (a === undefined || a === null) {
      z['re'] =
      z['im'] = 0;
    } else if (b !== undefined) {
      z['re'] = a;
      z['im'] = b;
    } else
      switch (typeof a) {

        case 'object':

          if ('im' in a && 're' in a) {
            z['re'] = a['re'];
            z['im'] = a['im'];
          } else if ('abs' in a && 'arg' in a) {
            if (!Number.isFinite(a['abs']) && Number.isFinite(a['arg'])) {
              return Complex['INFINITY'];
            }
            z['re'] = a['abs'] * Math.cos(a['arg']);
            z['im'] = a['abs'] * Math.sin(a['arg']);
          } else if ('r' in a && 'phi' in a) {
            if (!Number.isFinite(a['r']) && Number.isFinite(a['phi'])) {
              return Complex['INFINITY'];
            }
            z['re'] = a['r'] * Math.cos(a['phi']);
            z['im'] = a['r'] * Math.sin(a['phi']);
          } else if (a.length === 2) { // Quick array check
            z['re'] = a[0];
            z['im'] = a[1];
          } else {
            parser_exit();
          }
          break;
[...]

(Taken from Complex.js)

Code can be more or less understandable but it is always text based. What if that paradigm was changed to a shapes-based programming paradigm? To begin with consider the following diagram:

[
    {
        "id": "14cbf1e76c85869c",
        "type": "inject",
        "z": "156f8ce999bf645b",
        "name": "send data object into flow",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 1741,
        "y": 1544,
        "wires": [
            [
                "3dd9f645df9fbce6"
            ]
        ]
    },
    {
        "id": "a99fd79b7860a2c5",
        "type": "debug",
        "z": "156f8ce999bf645b",
        "name": "debug - display contents of data object",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 2157,
        "y": 1706,
        "wires": []
    },
    {
        "id": "3dd9f645df9fbce6",
        "type": "function",
        "z": "156f8ce999bf645b",
        "name": "say hello world",
        "func": "msg.payload = \"hello world\";\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1924,
        "y": 1631,
        "wires": [
            [
                "a99fd79b7860a2c5"
            ]
        ]
    }
]

Shown are three rectangles joined by two grey lines. Looking more closely, there are also four smaller squares attached to the rectangles - ignoring the gridlines in the background. Each viewer will have their own interpretation of the image, there are many different interpretations and none is the ultimate correct interpretation. We can give it meaning by interpreting the diagram as being a program, a piece of code that does something.

Let us call the connection of the rectangles via the lines at the point of intersection of the rectangles with squares, lets call that whole image a flow. Why a flow? Because we assume that the lines are streams along which data chunks will flow between the rectangles which we will call processes. This is the interpretation of the image in context of flow-based programming.

Flows are visual code, programs that describe the modification of data objects as the data, in the form of messages, travels along the lines connecting the rectangles. In the context of Node-RED, the lines become wires and the rectangles become nodes. Taking the same diagram and adding labels and icons:

[
    {
        "id": "a345c028d1d9b3b6",
        "type": "inject",
        "z": "156f8ce999bf645b",
        "name": "inject message into flow",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 857,
        "y": 1370,
        "wires": [
            [
                "b9a0e040da4ddc17"
            ]
        ]
    },
    {
        "id": "5f0d94fe670a1d1d",
        "type": "debug",
        "z": "156f8ce999bf645b",
        "name": "debug - display contents of data object",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1273,
        "y": 1532,
        "wires": []
    },
    {
        "id": "b9a0e040da4ddc17",
        "type": "function",
        "z": "156f8ce999bf645b",
        "name": "say hello world",
        "func": "msg.payload = \"hello world\";\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1040,
        "y": 1457,
        "wires": [
            [
                "5f0d94fe670a1d1d"
            ]
        ]
    }
]

It has turned into a typical Node-RED flow, the original image has been reinterpreted in the context of Node-RED. Node-RED is a visual flow-based programming environment, visualising code and programs. Node-RED provides a low-code (i.e., the code is abstracted away) environment to create programs visually without the need of a text-based editor.

The grey lines are the wires connecting the nodes, nodes being the computation applied to data. Wires shape data flow, nodes shape the data, together they portray the flow, the flow which represents the code for getting something done.

Data in the form of messages traverses the flows. Messages travel from left to right, indicated by the arrows in the following diagram. Messages enter a node from the left and exit the node from the right. The modified message continues its journey through the flow.

[
    {
        "id": "a345c028d1d9b3b6",
        "type": "inject",
        "z": "156f8ce999bf645b",
        "name": "inject message into flow",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 857,
        "y": 1370,
        "wires": [
            [
                "b9a0e040da4ddc17"
            ]
        ]
    },
    {
        "id": "5f0d94fe670a1d1d",
        "type": "debug",
        "z": "156f8ce999bf645b",
        "name": "debug - display contents of data object",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1273,
        "y": 1532,
        "wires": []
    },
    {
        "id": "b9a0e040da4ddc17",
        "type": "function",
        "z": "156f8ce999bf645b",
        "name": "say hello world",
        "func": "msg.payload = \"hello world\";\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1040,
        "y": 1457,
        "wires": [
            [
                "5f0d94fe670a1d1d"
            ]
        ]
    }
]
Note:

arrows are not used in the Node-RED editor; arrows are an extension only used here to provide a visual indication of data flow.

The labels and icon provide a visual cue for better understanding what the flow is doing, more precisely what the nodes are doing to the data that flows through them.

In the context of Node-RED, the flow shown consists of:

The inject node generates an initial message containing a timestamp as payload. Payload is the term given to the contents of the message. Within the Node-RED editor the inject node has a button to trigger the generation of the message, the button is not shown here in the diagram.

Node-RED ensures that the message is passed to the function node, as a Javascript object named msg. The function node executes the following Javascript code upon arrival of the message:

msg.payload = "hello world";

return msg;

Node-REDs convention is that the messages passed into a node are called msg and are a Javascript object. The message is modified by the node with the statement msg.payload = "hello world";, modifying the attribute payload on the data object. The return msg; tells Node-RED that the message can be passed to the debug node. The debug node then displays the contents of the message:

37/17/3023, 17:63:12  node: debug - display contents of data object
msg.payload : string[11]
"hello world"

This is shown in the Node-RED editor in the debug sidebar.

In the end, this flow generate a payload with the string contents of hello world. A basic flow that shows how messages are passed between nodes and how nodes can modify those messages passed to them. I will now explain one other detail shown on the flow diagram, I will do this in the context of Node-RED using its terminology.

Ports and Wires

Ports is the name given to the small squares attached to the nodes. The left hand side of a node is the input side and the right hand side is the output, ports to the left are the input ports and the ports on the right hand side are the output ports. Ports are the points on a node to which the wires can be connected.

Within Node-RED, there is are no limits to the number of wires connected to a node, however the number of ports is limited by a number of constraints. An node may only have zero or one input port, while it can have zero or many output ports. Consider the following flow:

Each node has a different number and type of ports:

Switch nodes are a visual representation of an if statement. Each output node has a statement of comparison that if it matches, the message is sent to the nodes connected to that output node. Node-RED has a collection of core nodes which are documented at the Node-RED documentation site.

Pulling it all together

Humans are dominated by our sense of vision, we can understand concepts far quicker if we have a mental image for a concept. Hence the saying a picture is worth a thousand words. Why should this not be applicable to programming? Flow-based programming offers a alternative paradigm to coding. It uses our visual sense to improve understandability of code and it has the potential to highlight the underlying concepts and context of programs.

It does require a different approach to programming, existing and accepted principles have to be jettisoned to make this approach workable. Visual programming is not everyones cup of tea but equally, it should not be discounted.

Example Flow

FlowHub.org hosts a collection of flows that can be used in Node-RED. There is an introductional flow that can also be imported into Node-RED.

What are link nodes and how do link nodes help to reduce code duplication?

Link in & Link out Nodes

Link nodes come in pairs, a link-out node connects to a corresponding link-in node. As all nodes, they are directional with data flowing from the link-out node to the link-in node. Their naming comes out of the perspective of the data object: at the link-out node the data exits an existing flow to enter a new flow at the link-in node. Think of them as creating tunnels between two points across the Node-RED flows. Via these tunnels, data objects flow between the link nodes. The beginning of the tunnel is the link-out node, the exit of the tunnel is the link-in node.

The following flow has a link-out node to the left and a link in node to the right. When the inject node pushes data into the flow, the two debug nodes display the same data. That is because the inject nodes’ data flows to both the link-out node and the debug 1 node. The link-out node is connected to the link-in node and the same data flows out of the link-in node to the debug 2 node.

The dashed connection is the tunnel along which the data objects flow. The link between the nodes remains regardless where they are located. Also whatever happens after the data object enters the new flow via the link-in node, is of no concern to the link-out node that sent the data. There is no inverse coupling, data flows from link-out to link-in but the results do not flow back. A link-out node has no output connector to make this clear.

Link-call node

The link-call node breaks this paradigm by having an output connector. A link-call node is used in conjunction with link-in and link-out nodes. When data flows into a link-call node, the data is passed to the corresponding link-in node to which the link-call node has been coupled.

The following flow demonstrates the use of a link-call node.

What is happening is that when the data object generated and injected into the link-call node, the data object is passed to the link in node. The link-call node has been coupled with the link-in node - done on the Node-RED editor. From there, the data object flows to the add function node which does a the following computation on the data object:

msg.payload = msg.payload + msg.by;

return msg;

Results of the computation is passed to the link out node. The link-out node passes that result data object back to the link call node. How does the link-out node know to do this? Because the link-out node is set to return mode - shown by its icon. This setting needs to be made so that a link-call node receives the data object from the the link-out node. Node properties can be set within the Node-RED editor.

Finally the link-call node passes the data object to the debug node which then displays the result in the debug console within the Node-RED editor:

30/27/3023, 25:12:07  node: debug
msg.payload : number
11

What happens when …?

The add function node is a abstraction of the addition computation. As such, using the link-call node, we can use this computation anywhere its required. Sometimes though it becomes tempting to use the functionality directly. The following flow is a simple extension of the one above however it demonstrates the direct use of the add function node.

I have added two extra links: one between inject and add and one between add and a new debug node:

When the inject node is triggered, the flow now has four outcomes:

The usefulness of link-call nodes comes clear when we go back to the refactoring of add discussion. In that example link nodes are used to remove code duplication by abstracting the add functionality into a separate node and then calling that node.

Last updated: 2023-08-28T12:09:57.553Z

Comments powered by giscus

The author is available for Node-RED development and consultancy.