How to Modularize and Reuse Function Node Code in Node-RED?

  • I have a Node-RED flow where several function nodes contain more than 100 lines of JavaScript code each. Managing and writing unit tests for such large code blocks has become increasingly difficult.
  • I’m looking for a way to modularize or separate the function node code .
  1. Store the JavaScript code in external files for better maintainability.
  2. Reuse the same code across multiple function nodes wherever required.
  3. Easily write and run unit tests for the extracted code.

I have two ideas:

  1. Create a Separate Node for Each Function
  • "I have created a custom lower-case node to check if it's possible to create a separate node for each functionality instead of using function nodes and linking them together. Is this approach feasible in Node-RED?".
  1. Create External Modules and Import Them
  • Write the functions in external JavaScript modules, then import and use them in Node-RED function nodes.

Which approach is better? or any other method have?

In the past I've used one of the following approaches:

1. Put the function into a subflow

Advantages:

  • proper error handling using the catch node
  • make use of node status
  • export/import subflows for reuse in other Node-RED instances

Disadvantage:

  • you might end up with many subflows cluttering the left palette bar

2. Use link-calls

Advantages:

  • better organization
    • put your shared functions on a separate flow tabs --> you can hide and/or lock flow tabs!
    • no cluttering of the palette bar

Disadvantage:

  • error handling is more complicated
    • errors can't be caught by targeting the link-call node because you can't pass them to the link result node
    • you'd have to handle errors manually on each call side by evaluation for an error property... too much boilerplate for me :see_no_evil:
  • no node status on the calling side

There was a discussing regarding the error handling with link-calls, but no conclusion has been reached: Capturing errors caused inside link-call flows - #5 by kuema

That's why I prefer the subflow solution, because I like clean error handling using "try-catch" :nerd_face:

2 Likes

@kuema

  • I have more than 100 lines of code inside a Function Node and want to handle that logic outside of the node for unit testing or modularity, i can move the logic into an external JavaScript file (module) or Create a Separate Node for Each Function .
  • This approach makes it easier to test and maintain.Which approach is better? or any other way?

Both are valid approaches. Depends on how many different custom nodes you'd need to create.

So the first solution might be easier.
You can use the module import of the function node to include your custom code.

Keep in mind that you need to restart Node-RED after making changes for both solutions. You wouldn't need to restart if using the subflow or link-call approach.

1 Like
  • Currently, i am using a more than 10 function node and it has more than 100 code; it's hard to write unit test case
  • Still, i am confucion
  • I can use the External Modules and Import of the function node to include my custom code. or follow this method Creating your first node : Node-RED

I followed this link and i did.
Creating your first node : Node-RED
so I want to crate more custom node like lowe-case node in left side panel, right?

Now try running the function node code outside of Node-RED

is this good method for production?

Depending on your needs ofcourse; you can create a npm module which doesnt necessarily have to become a node-red node, but it becomes importable for a function node (see the setup tab within a function node).

You could also expose your functions are flow/global context objects and call them when needed.
Personally I like link-call a lot, but as kuema indicated, debugging is more complicated.

Subflows are also an option, but keep in mind that each subflow node becomes an instance (ie, not efficient).

A module is certainly the way to go for this. Alternatively, you can create a plugin instead of a full node. In that case, you could even attach your code to the RED object that gets exposed to function nodes.

IMHO, don't even think about subflows (they introduce many issues). What I do to modularize my code is pack my parameters in the msg and use link calls to call other function nodes as if they were procedures in my code. Note however that calling an external node is an async operation, so beware of race conditions.

Can you explain what you mean by an external node please?

For functions that I use a lot like date/time formatting, I have a "master" function note that is called at start up and creates a global variable.

If i then want to use that function in my other nodes, I use call it

const func = global.get("functions")

then do something like:

func.formatDate( new Date() );

I'm just referring to other function nodes, i.e. I extract reusable pieces of code from my function node into independent standalone function nodes, serving as general-purpose modules which I can call from multiple places with call-links.

In that case they will not be asynchronous any more than an inline function node is asynchronous.

As functions in stored in global context , when called without async/await (or promise or callback) they will be synchronous.

Functions called by link-call will be async in relation to other flow nodes (but for all intents and purposes when in a single (in series) flow) they will appear to be synchronous from first node input to last node output.

In other words, splitting functions out to reusable subroutines that are graphically called via link-call is in line with the asynchronous advantages of node js and the visual aspect of flow based programming.

2 Likes

Hi @kuema, thank you for breaking down those approaches so clearly. I'm like you in that I use both link calls and subflows, but I use them for different things. Bear in mind, I'm still learning.

Link-call approach

I started out using a single flow to contain all of my "function" link-calls: all of the logic that gets constantly reused. In my case, it's mostly things like data reformatting (link return) or iterating through elements/arrays (do this thing to all of these) that publish out MQTT instead of returning anything.

I also use link calls for querying states of various things. I handle global variables by storing the latest message for each topic in a large constant global.

One use for this approach is querying the state of all sensors within a group whenever any individual sensor triggers; this gets fed to a bayesian filter that judges if a room is occupied and responds yay or nay.

The link-call approach has served me well for these kinds of use-cases.

Subflow approach

Then I discovered subflows. I can't speak to intention, but from my perspective as a user, it seemed like subflows were an accessible way for people of my basic skill level to create reusable nodes.

A subflow, for example, turned out to be the best way to handle the data inputs of a security camera. All messages related to that camera go into a subflow, which judges what to do, and critically, responds on different outputs.

Is it a human? Do the stuff that should be done if it's a human, might be functions, might be link-calls, and then send the result out of output 1.

Is it a cat? Do cat stuff, send out of output 2.

Is it a dog? Do dog stuff, which is different to cat stuff, but also send out of output 2.

That's pretty versatile.

Since you mentioned error catching, I link strategic points within a subflow to a final output that I'll connect to a debug node outside the subflow. I don't know if that's optimal or not, but it's worked so far.

I use them for different things
Broadly, it's to do with number of outputs.

  • I use link-calls for stuff that will do a lot of thinking and then return a single piece of data.
  • I use sub-flows for things that will process data and then send it via multiple outputs; in practise very much like writing a custom node.