Some thoughts for people who like `function` nodes and coding

Hey folks.

I know it has been sort of mentioned before in this tread

But I think this is a bit of a different perspective on it.

I am still very new to all this and some stuff I am seeing/learning these days is still WAY ABOVE me and my understanding.

I have also seen this link/video which is very interesting too.

And I am about to go against all it says. But why not? :wink:

If you are new to it all, you don't understand the big picture and I know it is easy to fall into habits which can (or not) be bad for you in the future.

BTW, I am NO past experience with GIT backups and the like. All too new for me.

So, you are writing a function node to do something.

WHERE DO YOU START??

Now, before I get to the STRUCTURE of the code, how about a few ideas to help you track the version of the code?

A HUGE thing that I am starting to do when writing code is to add a Header with the date/time when it was written.

eg:

//  2025 11 15 1450

Year, month, date and time.
It isn't Rocket science but gee, it goes a long way to keeping track of the code.

Yes, my flows are littered with backups of function nodes as I am developing them.
So what? It isn't illegal.

You write the code with this Header and copy it to a spare part of the flow - backup.
Test it. It works. Great, improve it.
When you've done the improvements, edit the header with the new time and repeat.

The times don't have to be THAT ACCURATE, but they help you see which is which.

Why so many backups? I hear you ask.

Well - my take on it - you don't always know where you are going with the code.
You are TRYING things. Seeing if they work.
And you may be several layers down and find this path is a dead end.

You need to go back a few steps to get rid of the stuff you added when you went down this path.
Back to a Good working copy then start again.
Of course you need to delete all the dead end path nodes. Helps reduce the clutter.

Then you start a new line of thought.
And it may happen multiple times.

When all is working - FINALLY - you can delete all the old copies.
But WHEN you delete them depends on how confident (and good your testing is) at that point in time.

A lot of things may not have been considered and bite you later.
So keeping some older versions for a few .... days/weeks/...... may be a good idea.

Try to group things.
There's no exact method I'm going to say, but maybe this way:

All functions together at the top
All configurable constants / definitions next
Then the main execution flow
Then outputs / status / return

It has been said that for every line of code there are FIVE lines of comments to accompany it.
I'm not going to quote exact numbers. But I do agree that a well laid out structure is helpful.

Break up the blocks with nice lines like:

==================================================
// New function block description here

As an example.

And maybe even add comments at the top which describe what the block is doing and a brief walk through of how it works.

Although the clip above mentions how DEEP your tests should be (maximum)
Yes, try to keep your conditional testing from being too deep.

Cut things down and have a lot of shallower testing happen before the code so if you want to change things it is at these shallower depths you are changing things.

I know it can be hard to see/understand all this - and believe me, I am still coming to terms with it - it really can/does help.

Hope this is helpful to at least one person.
(Though more would be nicer.)

All the best.
Happy coding.

Disclaimer.
A lot of this is what I've learnt from getting ChatGPT to help me and me getting it to optimise the code and stuff like that.

Compared to the mess/dog's breakfast I had, the new code is so much more compact and clearer to read.

So I think it is only fair I share what I've learnt with others and Pay it forward.

Oh, just remembered some other stuff to help too.

I've been caught a few times with variable names.

If they are not going to be used further downstream, convert them all to either upper or lower case when parse.

Eg:

// Iterate through the message properties and convert all keys to lowercase
Object.keys(msg).forEach(key => {
    // Convert key to lowercase and update the message
    msg[key.toLowerCase()] = msg[key];
    // Delete the old key (in original case) to avoid duplicates
    if (key !== key.toLowerCase()) {
        delete msg[key];
    }
});

Because now and then your finger may not clear the <shift> key quick enough and you don't see the wrong name.
It causes heaps of headaches weeks/months later.

This is a good discipline for sure. But while you are at it, if you think you will need to explain to yourself, or some future person, what it is about, expand that header with who wrote it and WHY.

Have a look at https://jsdoc.app for some ideas around standardising JavaScript self-documentation.

One thing I would add is not to forget that Node-RED is great at prototyping. I will often start by using the standard (and a few custom) nodes but then when I understand how I want the flow to actually work, if it has a lot of nodes in it and I've more to do, I may choose to condense those nodes into a function node now that I know how the processing should go. This is sometimes much easier than puzzling over JavaScript code logic. But sometimes I will go the other way. And sometimes I will start and finish either way!

That's kind of the point really, there isn't a right and wrong way to do things. Well, there kind of is, if it works well enough, that is probably the right way, at least for now. :smiley:

I guess this is a case of how experienced you are writing JavaScript and other languages. To me, I always (in JavaScript) use camelCase for variables and properties, just because I've become so used to it and I do it without thinking. For class names, I use PascalCase.

The key thing is to be consistent always. If you are working with others, then you all need to work to the same consistency (and in that case, you need to document it as well).

When you work on code in something like VSCode, one of the nice things is being able to define your standard "linting" to help maintain consistency. I REALLY wish we could use eslint in function nodes. Another advantage to working in VSCode is the ability to integrate AI and provide a copilot-instructions.md file which describes to the AI how you want it to behave. These can be quite long and also become useful guides for humans as well.

Thanks for sharing your thoughts to the community, these ideas are always welcome.

1 Like

Learn git and you won't do such things. It's like doing version control by creating files with dates, i.e. code-version-1.js then code-version-2.js - great and the perfect solution if you don't know how to use git (or any other versioning software - svn, cvs, rcs, mercurial,...). But once you do, you can easily get the dates of your changes by doing git log - no more extra stuff in source code files. (even cooler git whatchanged to see what changed a file and when for its entire lifetime).

Exactly! NR is great for copy & paste - create a function node, write some code. Want to change that code? Copy and paste the node and make the changes. Keep each function node as you copy and paste and you have your version control - visually :wink: Create a group to put all the old versions (i.e. function node) and e'Voila, you're doing it right! I say this in all seriousness because that's very much doable in NR, I would never do this using textual programming but copying nodes and keeping them around is super simple in NR.

But because I come from git and proper versioning, I created flowhub.org to do exactly that - visually version control my visual flow code. I don't want collection of old code cluttering my flows, so my old code is versioned off into a git repository via a button inside NR - done.

I would go the opposite direction - I would create my own node for specific functionality.

Or perhaps one step on from what you're saying: from those function nodes, I would definitely go to creating my own nodes to cover the functionality. Hence my nodedev collection of nodes so that I can create those nodes inside of NR. The difficult (or rather the hurdle) is the overhead of creating the boiler-plate code for new nodes. So it's simpler to stick with the function nodes. This I find a mistake.

The nodedev nodes are designed to generate the boiler plate and allows me to focus on pushing functionality into my own nodes. It's taking the subflow approach to the logical conclusion of generating node packages inside NR.

Anyway, it's good that folks are thinking about these things and how to program in an organised manner but always remember: you're not the first - there are a bunch of learnings from others who came before us. That's why there tools for source versioning, that's why there are style guides for how code should be formatted, that's why projects have certain directory structures, that's why makefiles exist ... etc

1 Like

I don't really see how that can help when using vanilla function nodes and editing in the Editor. For more complex code, you probably don't want to just rely on git control of the flow file, too many variables. AFAIK, there is currently no way to apply git to a function node - something to think about for the future.

Of course, that is another option. However, one that is not open to everyone and even I wouldn't quickly decide to do that given time constraints.

Indeed. I have some templates for new nodes but it is still quite a bit of effort. I really should create a generator - another future project. :face_with_raised_eyebrow:

Neither is ten finger typing, one can do it just as well with two. But once one has learnt to type with ten fingers, one doesn't go back to two. So it is when one learns a new skill. Git is a skill. You learn, you understand, and you might use the principles (managing change) in another context.

My point was to learn different skills so that you can make use of those skills. I learnt to do structure version management using tools, I would not go back to putting dates into source codes because I have tools that do that for me. I can still do that if it is necessary, and perhaps NR is a case in point where it is necessary to go back to putting dates into source code.

But then the question becomes how can I improve my workflow so that I don't have manage dates and instead its done automatically:

RED.events.on("nodes:change", (node) => {
        var dtStamp = (new Date()).toISOString();

        if (node._def && node._def.defaults) {
            if (node._def.defaults.hasOwnProperty("createdAt") && node.createdAt === undefined) {
                node.createdAt = dtStamp;
            }

            if (node._def.defaults.hasOwnProperty("updatedAt") && (node.changed || node.updatedAt === undefined)) {
                node.updatedAt = dtStamp;
            }
        }
    });

I added that bit of code to update dates on my nodes (that have createdAt and/or updatedAt) to maintain information about when I change things. (Aside: one of the first thing we were taught about CVS was to use $Id$ at the top of files, that would be replaced with the current version. Some my emacs templates for new files still has that :smiley: - If I used that today ...)

Most of my function nodes are far too simplistic to require more formal documentation though they do often have lots of explanatory comments. I generally don't bother with date stamps though (1). For actual nodes, those are, of course semantically versioned for the most part, though a few modules may have date strings for versioning since they may change a lot more rapidly than the package - mostly things like my CSS files. Of course, git and GitHub do maintain the formal commits but version strings can still be useful - especially if you want/need runtime version checks.

And yes, I do have some automated created/updated strings - mostly in individual documentation (markdown) files, if nothing else, these are also used to generate runtime visible copywrite statements. Mostly I use a custom shortcut in VScode. It probably wouldn't be hard to create an Editor plugin though that could automatically create headers with author and created/updated dates. Not something I would use enough to make it worth creating myself but I can see that in more commercial settings, that might be a really good time-saver.


  1. I should point out that I very rarely ever go back to earlier versions so this isn't really an issue for me. I may, however, sometimes leave commented out code in place until I'm sure that the new code works. :smiley: