How to implement Node-RED node in TypeScript

eye-catchJavaScript/TypeScript

The official site describes how to implement our own node in JavaScript. However, I don’t find a page for TypeScript. I’ve implemented my own nodes many times but the type version of node-red was 0.20.x. To write this article, I learned how to implement it again. The versions are following.

"@types/node": "^12.20.16",
"@types/node-red": "^1.1.1",

The node that I created was node-red-contrib-password-generator.

Sponsored links

Overview to implement own Node-RED node

To create our own node, we need the following files.

FileFor
xxxxNode.htmlTo place on node list. Appearance.
xxxxNode.tsTo define actual behavior
xxxxNodeDef.tsTo define required parameters
package.jsonTo register the node to Node-RED

All node-related file names include “Node” in the file name but it’s not mandatory. The implementation is of course the same as the JavaScript version but we need to put types. The official page explains how to write an HTML file, so I don’t explain it.

Define node parameters

we need to register a callback via registerType function. The callback requires Node and Node definition. When the node requires special properties we need to define them to an interface based on NodeDef interface. We can implement without these definitions but we need to use any data type in this case.

// PasswordGeneratorNodeDef.ts
import * as nodered from "node-red";

export interface PasswordGeneratorNodeDef
    extends nodered.NodeDef {
    length: number;
    setTo?: string;
}

Define node behavior (Entry point)

We define the node behavior and set it to default export. The first argument of registerType is node type name. It must be the same name as the one in html. config variable in the callback is now our own data type that defines our own properties. Intellisense can support our coding by this.

// PasswordGeneratorNode.ts
import * as nodered from "node-red";
import { generatePassword } from "./PasswordGenerator";
import { PasswordGeneratorNodeDef } from "./PasswordGeneratorNodeDef";
import * as yutolity from "yutolity";

// it can't set to the input event listener now
// using any is workaround but not good
interface PayloadType extends nodered.NodeMessageInFlow {
    to: string;
}

export = (RED: nodered.NodeAPI): void => {
    RED.nodes.registerType("password-generator",
        function (this: nodered.Node, config: PasswordGeneratorNodeDef): void {
            RED.nodes.createNode(this, config);

            this.on("input", async (msg: any, send, done) => {
                const password = await generatePassword(config.length);
                const valueSetPath = msg.to || config.setTo || "payload";
                msg = yutolity.setValue(msg, valueSetPath, password);
                send(msg);
                done();
            });
        });
}

As I added a comment to the code, it is not possible to set our own data type to input event callback. It requires NodeMessageInFlow. We can’t set extended data type there since it is NOT T extends NodeMessageInFlow at the moment.

I recommend writing the main logic in another file because it is easier to write a test. We need to use node-red-node-test-helper to write node tests but it makes the tests a little bit unreadable. It is better to write tests without it if possible.

Add node-red entry to package.json

We need to add node-red entry to package.json to tell where the entry point is. It must be a JavaScript file. Set “xxxxNode.js” here.

"node-red": {
    "nodes": {
      "passwordGeneratorNode": "./dist/lib/PasswordGeneratorNode.js"
    }
}

Test own Node-RED node in Docker container

Firstly, we need to create a Docker image.

FROM nodered/node-red

COPY ../package.json ./password-generator/
COPY ../dist ./password-generator/dist
RUN npm install ./password-generator --unsafe-perm --no-update-notifier --no-fund --only=production

What we need to copy is package.json, compiled js, and html files. Then, install our own node by specifying the directory path. It’s better to exclude unrelated files in order to reduce the size of the Docker image. I don’t do it in my repository because it is just a test but you should do it if you need to release the Docker image.
Note that you must copy the files into the same directory specified in package.json. Otherwise, your node isn’t loaded. Node-RED loads a file from the path specified in package.json.

"node-red": {
    "nodes": {
      "passwordGeneratorNode": "./dist/lib/PasswordGeneratorNode.js"
    }
}

You need to change the path if you want to copy your files on the same directory as package.json like this below.

"node-red": {
    "nodes": {
      "passwordGeneratorNode": "./PasswordGeneratorNode.js"
    }
}

node doesn’t appear on the node list

In my first implementation, Node-RED doesn’t show my own node. No error message was shown in the log. It took me a while to recognize the reason.
I defined node appearance in a separated typescript file. It looks like this below.
CAUTION!! It doesn’t work!

// PasswordGeneratorNodeInit.ts
import * as nodered from "node-red";
import { PasswordGeneratorNodeProperties } from "./PasswordGeneratorNodeDef";

declare const RED: nodered.EditorRED;
const nodeName = "password-generator";

RED.nodes.registerType<PasswordGeneratorNodeProperties>(nodeName, {
    category: "function",
    color: "#a6bbcf",
    defaults: {
        name: { value: "" },
        length: { value: "", required: true, validate: RED.validators.number() },
        setTo: { value: "" },
    },
    inputs: 1,
    outputs: 1,
    label: function () {
        return this.name || nodeName;
    },
});

I extracted the code from html file because IntelliSense can support coding. However, it didn’t work. Node-RED didn’t complete to load nodes when I defined the following code in html file. It froze loading.

<script src="PasswordGeneratorNodeInit.js"></script>

Hmm… Is there any good way to define node appearance in TypeScript? Please leave a comment if you know it.

End

The interface structure changed from version 0.20.X. There is no official page to explain how to implement our own node in TypeScript. Therefore, I’ve read the type definitions’ code in Node-RED. It took longer than I expected but I hope this article helps others.

Go to my repository if you need complete code.

GitHub - yuto-yuto/node-red-contrib-password-generator: Password generator for Node-RED node
Password generator for Node-RED node. Contribute to yuto-yuto/node-red-contrib-password-generator development by creating an account on GitHub.

You will create your own flow file and eventually want to have test for the flow. This is the complete guide for you.

Comments

Copied title and URL