CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-node-red

Low-code programming platform for event-driven applications with visual flow-based editor and runtime system

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

node-development.mddocs/

Node Development

APIs and patterns for creating custom Node-RED node types. This covers the node registration system, lifecycle management, and integration with the Node-RED runtime for building reusable node modules.

Capabilities

Node Registration

Core API for registering new node types with the Node-RED runtime.

/**
 * Node registration function (available in node modules)
 * @param type - Node type identifier
 * @param constructor - Node constructor function
 */
module.exports = function(RED) {
    RED.nodes.registerType(type: string, constructor: NodeConstructor): void;
};

/**
 * Node constructor interface
 */
interface NodeConstructor {
    (this: NodeInstance, config: NodeConfig): void;
}

/**
 * Get node instance by ID
 * @param id - Node instance ID
 * @returns Node instance or null
 */
RED.nodes.getNode(id: string): NodeInstance | null;

/**
 * Create and initialize node instance
 * @param node - Node instance object
 * @param config - Node configuration from editor
 */
RED.nodes.createNode(node: NodeInstance, config: NodeConfig): void;

Basic Node Example:

module.exports = function(RED) {
    function MyCustomNode(config) {
        // Create the node instance
        RED.nodes.createNode(this, config);
        
        // Store configuration
        this.customProperty = config.customProperty;
        
        // Set up message handler  
        this.on('input', function(msg, send, done) {
            // Process the message
            msg.payload = this.customProperty + ": " + msg.payload;
            
            // Send the message
            send(msg);
            
            // Signal completion
            done();
        });
        
        // Clean up on close
        this.on('close', function() {
            // Cleanup code here
        });
    }
    
    // Register the node type
    RED.nodes.registerType("my-custom-node", MyCustomNode);
};

Node Instance Interface

Methods and properties available on node instances during execution.

/**
 * Node instance interface
 */
interface NodeInstance extends EventEmitter {
    id: string;
    type: string;
    name?: string;
    send(msg: NodeMessage | NodeMessage[]): void;
    error(err: string | Error, msg?: NodeMessage): void;
    warn(warning: string | object): void;
    log(info: string | object): void;
    status(status: NodeStatus): void;
    on(event: 'input', handler: InputHandler): void;
    on(event: 'close', handler: CloseHandler): void;
    context(): ContextStore;
}

/**
 * Input message handler
 */
interface InputHandler {
    (msg: NodeMessage, send: SendFunction, done: DoneFunction): void;
}

/**
 * Send function for output messages
 */
interface SendFunction {
    (msg: NodeMessage | NodeMessage[]): void;
}

/**
 * Done function to signal message processing completion
 */
interface DoneFunction {
    (err?: Error): void;
}

/**
 * Close handler for cleanup
 */
interface CloseHandler {
    (removed: boolean, done: () => void): void;
}

Advanced Node Example:

module.exports = function(RED) {
    function AdvancedNode(config) {
        RED.nodes.createNode(this, config);
        
        const node = this;
        let processing = false;
        
        // Configuration
        node.timeout = parseInt(config.timeout) || 5000;
        node.retries = parseInt(config.retries) || 3;
        
        // Status update
        node.status({ fill: "green", shape: "ring", text: "ready" });
        
        node.on('input', function(msg, send, done) {
            // Prevent concurrent processing
            if (processing) {
                node.warn("Already processing, dropping message");
                done();
                return;
            }
            
            processing = true;
            node.status({ fill: "blue", shape: "dot", text: "processing" });
            
            // Simulate async operation with timeout
            const timeout = setTimeout(() => {
                processing = false;
                node.status({ fill: "red", shape: "ring", text: "timeout" });
                done(new Error("Processing timeout"));
            }, node.timeout);
            
            // Async processing
            processMessage(msg).then(result => {
                clearTimeout(timeout);
                processing = false;
                
                msg.payload = result;
                node.status({ fill: "green", shape: "dot", text: "success" });
                
                send(msg);
                done();
            }).catch(err => {
                clearTimeout(timeout);
                processing = false;
                
                node.status({ fill: "red", shape: "ring", text: "error" });
                done(err);
            });
        });
        
        node.on('close', function(removed, done) {
            // Clean up resources
            processing = false;
            node.status({});
            
            if (removed) {
                node.log("Node removed from flow");
            }
            
            done();
        });
        
        async function processMessage(msg) {
            // Custom processing logic
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve("Processed: " + msg.payload);
                }, 1000);
            });
        }
    }
    
    RED.nodes.registerType("advanced-node", AdvancedNode);
};

Configuration Nodes

Special nodes that provide shared configuration across multiple node instances.

/**
 * Configuration node constructor
 */
interface ConfigNodeConstructor {
    (this: ConfigNodeInstance, config: NodeConfig): void;
}

/**
 * Configuration node instance
 */
interface ConfigNodeInstance {
    id: string;
    type: string;
    name?: string;
    users: string[];
    [key: string]: any;
}

Configuration Node Example:

module.exports = function(RED) {
    // Configuration node for API credentials
    function ApiConfigNode(config) {
        RED.nodes.createNode(this, config);
        
        this.host = config.host;
        this.port = config.port;
        // Store credentials securely
        this.username = this.credentials.username;
        this.password = this.credentials.password;
        
        // Create reusable client
        this.client = createApiClient({
            host: this.host,
            port: this.port,
            username: this.username,
            password: this.password
        });
    }
    
    // Register config node
    RED.nodes.registerType("api-config", ApiConfigNode, {
        credentials: {
            username: { type: "text" },
            password: { type: "password" }
        }
    });
    
    // Node that uses the config
    function ApiRequestNode(config) {
        RED.nodes.createNode(this, config);
        
        // Get config node instance
        this.apiConfig = RED.nodes.getNode(config.apiConfig);
        
        if (!this.apiConfig) {
            this.error("API configuration not found");
            return;
        }
        
        this.on('input', function(msg, send, done) {
            // Use shared client from config node
            this.apiConfig.client.request(msg.payload)
                .then(response => {
                    msg.payload = response;
                    send(msg);
                    done();
                })
                .catch(err => done(err));
        });
    }
    
    RED.nodes.registerType("api-request", ApiRequestNode);
};

Context and Storage

Access to Node-RED's context storage system from custom nodes.

/**
 * Get node context storage
 * @returns Context store for this node
 */
node.context(): ContextStore;

/**
 * Get flow context storage
 * @returns Context store for the current flow
 */
RED.util.getContext('flow', flowId): ContextStore;

/**
 * Get global context storage
 * @returns Global context store
 */
RED.util.getContext('global'): ContextStore;

Context Usage in Nodes:

function StatefulNode(config) {
    RED.nodes.createNode(this, config);
    
    const node = this;
    
    node.on('input', function(msg, send, done) {
        const context = node.context();
        
        // Get stored state
        let counter = context.get("counter") || 0;
        counter++;
        
        // Update state
        context.set("counter", counter);
        
        // Add state to message
        msg.counter = counter;
        msg.payload = `Message #${counter}: ${msg.payload}`;
        
        send(msg);
        done();
    });
}

Message Handling Patterns

Common patterns for handling messages in custom nodes.

/**
 * Multiple output node pattern
 */
function SplitterNode(config) {
    RED.nodes.createNode(this, config);
    
    this.on('input', function(msg, send, done) {
        const outputs = [];
        
        // Prepare outputs array (one per output terminal)
        for (let i = 0; i < config.outputs; i++) {
            outputs[i] = null;
        }
        
        // Route message based on payload type
        if (typeof msg.payload === 'string') {
            outputs[0] = msg; // String output
        } else if (typeof msg.payload === 'number') {
            outputs[1] = msg; // Number output
        } else {
            outputs[2] = msg; // Other output
        }
        
        send(outputs);
        done();
    });
}

/**
 * Batch processing node pattern
 */
function BatchNode(config) {
    RED.nodes.createNode(this, config);
    
    const node = this;
    const batchSize = config.batchSize || 10;
    let batch = [];
    
    node.on('input', function(msg, send, done) {
        batch.push(msg);
        
        if (batch.length >= batchSize) {
            // Send batch
            const batchMsg = {
                payload: batch.map(m => m.payload),
                _msgid: RED.util.generateId()
            };
            
            send(batchMsg);
            batch = []; // Reset batch
        }
        
        done();
    });
    
    // Timer to flush partial batches
    const flushTimer = setInterval(() => {
        if (batch.length > 0) {
            const batchMsg = {
                payload: batch.map(m => m.payload),
                _msgid: RED.util.generateId()
            };
            
            node.send(batchMsg);
            batch = [];
        }
    }, 30000); // Flush every 30 seconds
    
    node.on('close', function() {
        clearInterval(flushTimer);
    });
}

Node Module Structure

Structure and metadata for Node-RED node modules.

/**
 * Node module package.json requirements
 */
interface NodeModulePackage {
    name: string;
    version: string;
    description: string;
    "node-red": {
        version?: string;
        nodes: {
            [nodeType: string]: string; // Path to node JS file
        };
    };
    keywords: string[]; // Should include "node-red"
}

Example package.json:

{
    "name": "node-red-contrib-my-nodes",
    "version": "1.0.0",
    "description": "Custom nodes for Node-RED",
    "node-red": {
        "nodes": {
            "my-custom-node": "nodes/my-custom-node.js",
            "api-config": "nodes/api-config.js"
        }
    },
    "keywords": [
        "node-red",
        "api",
        "custom"
    ],
    "files": [
        "nodes/"
    ]
}

Types

interface NodeConfig {
    id: string;
    type: string;
    name?: string;
    [key: string]: any;
}

interface NodeMessage {
    _msgid: string;
    topic?: string;
    payload: any;
    [key: string]: any;
}

interface NodeStatus {
    fill?: 'red' | 'green' | 'yellow' | 'blue' | 'grey';
    shape?: 'ring' | 'dot';
    text?: string;
}

interface ContextStore {
    get(key: string | string[], store?: string, callback?: Function): any;
    set(key: string | string[], value: any, store?: string, callback?: Function): void;
    keys(store?: string, callback?: Function): string[];
}

docs

application.md

cli.md

function-nodes.md

http-services.md

index.md

node-development.md

runtime.md

utilities.md

tile.json