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.
Overview to implement own Node-RED node
To create our own node, we need the following files.
File | For |
---|---|
xxxxNode.html | To place on node list. Appearance. |
xxxxNode.ts | To define actual behavior |
xxxxNodeDef.ts | To define required parameters |
package.json | To 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.
You will create your own flow file and eventually want to have test for the flow. This is the complete guide for you.
Comments