Ein Flow soll Nachrichten auf verschiedene Ausgänge verteilen, Systeminformationen abfragen oder auf das Ergebnis eines Dateizugriffs warten. Mit der Node-RED Function Node lässt sich dafür eigener JavaScript-Code direkt in die Verarbeitung einbauen. Dabei kommt es vor allem darauf an, wie der Code mit den Nachrichten und der Node-RED-Laufzeit zusammenarbeitet.
In diesem Beitrag zeige ich dir die Rückgabe über mehrere Ausgänge, das Einbinden der Node.js-Module os und fs sowie das asynchrone Senden mit node.send(). Zum Einstieg vergleichen wir eine einfache Berechnung mit der Change Node. JavaScript-Grundkenntnisse setze ich dabei voraus.
Was macht die Function Node in Node-RED?
Node-RED bringt für viele Aufgaben bereits passende Nodes mit. Werte lassen sich verändern, Nachrichten nach Bedingungen aufteilen und Daten für die nächste Verarbeitung vorbereiten. Eine Function Node ergänzt diese Möglichkeiten um eigenen JavaScript-Code.
Der Code im Reiter Funktion wird für jede eingehende Nachricht ausgeführt. Über msg greifen wir auf diese Nachricht zu. Anschließend geben wir sie mit return msg; weiter.
Für einfache Aufgaben lohnt sich allerdings ein Blick auf die anderen Nodes:
| Aufgabe | Passende Node |
|---|---|
| Eine Eigenschaft setzen, verschieben oder löschen | Change |
| Nachrichten anhand einer Bedingung auf Ausgänge verteilen | Switch |
| Mehrere Berechnungen und Bedingungen mit JavaScript kombinieren | Function |
Die Function Node ist deshalb nicht automatisch die bessere Wahl. Eine einzelne Temperaturprüfung bleibt mit einer Switch Node oft gut nachvollziehbar. Interessant wird JavaScript vor allem dann, wenn mehrere Berechnungen und Entscheidungen zusammengehören. Das lässt sich bereits an einem kleinen Sensorbeispiel zeigen.
Der erste Flow: Inject, Function und Debug
Für den Einstieg genügt ein Flow aus Inject, Function und Debug. Die Inject Node liefert die Zahl 21.5 in msg.payload. Im Reiter Funktion addieren wir einen beispielhaften Korrekturwert von 0,5 °C:
msg.payload = msg.payload + 0.5; return msg;
Mit return msg; gibt die Function Node die veränderte Nachricht weiter. Nach dem Übernehmen der Änderungen und dem Auslösen der Inject Node ist in der Debug Node die Zahl 22 zu erwarten.
Change oder Function: denselben Sensorwert auf zwei Wegen verändern
Braucht es für diese Berechnung überhaupt JavaScript? Zum Vergleich sendet eine Inject Node dieselbe Nachricht gleichzeitig an eine Change Node und eine Function Node. Beide Zweige enden an derselben Debug Node.
Für den direkten Vergleich liegt der Messwert in msg.sensor_value. Die Payload enthält eine Kennzeichnung des jeweiligen Zweigs. In den folgenden Beispielen verwenden wir wieder msg.payload für die Nutzdaten.
Die Inject Node setzt msg.sensor_value auf die Zahl 21.5 und msg.payload auf aus der Change Node.
Berechnung mit der Change Node
Die Change Node setzt msg.sensor_value auf das Ergebnis des folgenden JSONata-Ausdrucks:
sensor_value + 0.5
In der Change Node ist dafür JSONata als Ausdruckstyp hinterlegt. Der Ausdruck greift direkt über sensor_value auf den Messwert zu; ein vorangestelltes msg. wird hier nicht benötigt. Die Payload mit dem Text aus der Change Node bleibt erhalten.
Dieselbe Berechnung mit der Function Node
Im Reiter Funktion steht folgender JavaScript-Code:
msg.sensor_value = msg.sensor_value + 0.5; msg.payload = "aus der Function Node"; return msg;
Die erste Zeile erhöht den Sensorwert um 0.5. Anschließend wird die Payload zur Kennzeichnung dieses Zweigs geändert und die Nachricht weitergegeben.
Nach dem Auslösen erhält die Debug Node zwei Nachrichten mit sensor_value: 22. Eine trägt die Payload aus der Change Node, die andere aus der Function Node. Da die gesamte Nachricht angezeigt wird, sind Messwert und Kennzeichnung gemeinsam sichtbar. Die Reihenfolge der Ausgaben ist für den Vergleich unerheblich.
Für die einfache Addition genügt die Change Node. Die folgenden Beispiele zeigen, was die Function Node darüber hinaus bietet: mehrere Ausgänge, eingebundene Node.js-Module und asynchrone Verarbeitung.
Den vollständigen Vergleichsflow kannst du über die Importfunktion des Node-RED-Editors einfügen. Er enthält die Testwerte und benötigt keine zusätzliche Hardware.
Vergleichsflow mit Change und Function als JSON anzeigen
[
{
"id": "312e13b9e367f556",
"type": "tab",
"label": "Flow 3",
"disabled": false,
"info": "",
"env": []
},
{
"id": "b483af58d0c2346b",
"type": "inject",
"z": "312e13b9e367f556",
"name": "Beispiel - Sensorwert",
"props": [
{
"p": "sensor_value",
"v": "21.5",
"vt": "num"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "aus der Change Node",
"payloadType": "str",
"x": 140,
"y": 60,
"wires": [
[
"b33de94e434b6c37",
"e3344f275ca031b1"
]
]
},
{
"id": "b33de94e434b6c37",
"type": "change",
"z": "312e13b9e367f556",
"name": "",
"rules": [
{
"t": "set",
"p": "sensor_value",
"pt": "msg",
"to": "sensor_value + 0.5",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 510,
"y": 60,
"wires": [
[
"54097cef99de77c6"
]
]
},
{
"id": "54097cef99de77c6",
"type": "debug",
"z": "312e13b9e367f556",
"name": "Ausgabe des geänderten Sensorwertes",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 860,
"y": 60,
"wires": []
},
{
"id": "e3344f275ca031b1",
"type": "function",
"z": "312e13b9e367f556",
"name": "Increment des Sensorwertes",
"func": "msg.sensor_value = msg.sensor_value + 0.5;\nmsg.payload = \"aus der Function Node\";\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 500,
"y": 140,
"wires": [
[
"54097cef99de77c6"
]
]
}
]
Mehrere Ausgänge in der Function Node verwenden
Die Function Node erhält eine Nachricht als msg. Mit return msg; wird sie weitergegeben, mit return null; endet ihre Verarbeitung an dieser Node. Andere Flow-Zweige laufen davon unabhängig weiter. Wer nur die Payload ändern möchte, sollte die bestehende Nachricht weiterverwenden: So bleiben etwa msg.topic oder die für HTTP-Flows wichtigen Eigenschaften msg.req und msg.res erhalten.
Variante 1: Module direkt in der Function Node zuordnen
Für getrennte Verarbeitungswege lässt sich die Anzahl der Ausgänge im Bearbeitungsdialog auf zwei oder mehr erhöhen.
Jeder Ausgang wird mit einer eigenen Debug Node verbunden. Die Inject Node liefert für dieses Beispiel eine Zahl in msg.payload:
if (msg.payload > 25) {
return [null, msg];
}
return [msg, null];
Werte bis einschließlich 25 gehen an Ausgang 1, höhere Werte an Ausgang 2. Die Position im Array ordnet die Nachricht dem Ausgang zu; null lässt ihn frei. Das Array allein erzeugt keine zusätzlichen Anschlüsse: Die zwei Ausgänge müssen auch im Dialog eingestellt sein.
Mehrere Nachrichten an einem Ausgang werden als inneres Array übergeben.
Hier erhält Ausgang 1 zwei Nachrichten nacheinander, während Ausgang 2 frei bleibt:
return [[
{ payload: "erste Nachricht" },
{ payload: "zweite Nachricht" }
], null];
os und fs als Node.js-Module einbinden
Ein direktes require("os") oder require("fs") steht im Reiter Funktion nicht zur Verfügung. Die Function Node läuft in einer eigenen Ausführungsumgebung. Für die Bereitstellung der Module gibt es zwei alternative Wege: die Zuordnung direkt in der Node oder eine zentrale Freigabe in der settings.js. Für die Beispiele genügt einer dieser Wege.
Die Zuordnung direkt in der Function Node wird über functionExternalModules freigeschaltet. Die Einstellung gehört in das vorhandene exportierte Konfigurationsobjekt der verwendeten settings.js. Eine Änderung wird nach einem Neustart von Node-RED wirksam:
functionExternalModules: true,
Im Bearbeitungsdialog lassen sich anschließend Module und die zugehörigen Variablennamen festlegen. Für unsere Beispiele wird das Modul os der Variablen os und das Modul fs der Variablen fs zugeordnet. Beide gehören zu Node.js; eine zusätzliche npm-Installation ist dafür nicht nötig. Bei Drittanbieterpaketen sind dagegen die Freigaben und Installationsregeln der eigenen Node-RED-Konfiguration zu beachten.
Variante 2: Module zentral über functionGlobalContext bereitstellen
Alternativ werden die Module zentral in der vorhandenen Eigenschaft functionGlobalContext der settings.js ergänzt. Andere bereits vorhandene Einträge bleiben erhalten:
functionGlobalContext: {
osModule: require("os"),
fsModule: require("fs")
},
Nach dem Neustart greift die Function Node bei dieser Variante über den globalen Context darauf zu. Die folgenden Zeilen sind nur für diese zentrale Bereitstellung nötig; bei der Zuordnung im Dialog stehen die Variablen bereits bereit:
const os = global.get("osModule");
const fs = global.get("fsModule");
Systeminformationen mit os abfragen
Mit dem eingebundenen os-Modul lassen sich Informationen über das System abfragen, auf dem Node-RED läuft. Dafür verwenden wir einen separaten kleinen Flow:
Für einen separaten Test reicht eine Inject Node, die eine Function Node auslöst. Diese ersetzt die Payload durch aktuelle Systemdaten:
msg.payload = {
hostname: os.hostname(),
plattform: os.platform(),
laufzeitSekunden: Math.floor(os.uptime())
};
return msg;
Die nachgeschaltete Debug Node zeigt Hostname, Plattform und Betriebssystemlaufzeit. Die Werte stammen von dem Rechner, auf dem Node-RED läuft, nicht vom Browser-Rechner. os.uptime() beschreibt dabei die Laufzeit des Betriebssystems und nicht die Laufzeit des Flows.
Mit fs Dateien asynchron lesen
Bei einem Dateizugriff steht das Ergebnis erst später fest. Statt synchron zu warten, lesen wir mit fs.readFile() im Hintergrund. Wenn der Callback aufgerufen wird, gibt node.send() die Nachricht weiter. node.done() meldet der Node-RED-Laufzeit anschließend, dass die Verarbeitung dieser Eingangsnachricht abgeschlossen ist.
Das folgende Beispiel liest unter Fedora die Datei /etc/hostname. Voraussetzung ist das zuvor eingebundene fs-Modul. Die Function Node besitzt zwei Ausgänge: Ausgang 1 für den Dateiinhalt und Ausgang 2 für Fehler. Beide lassen sich zum Testen mit Debug Nodes verbinden:
fs.readFile("/etc/hostname", "utf8", (err, inhalt) => {
if (err) {
msg.payload = { datei: "/etc/hostname", fehler: err.message };
node.send([null, msg]);
node.done();
return;
}
msg.payload = inhalt.trim();
node.send([msg, null]);
node.done();
});
return;
Der erfolgreiche Lesevorgang liefert den Dateiinhalt an Ausgang 1. Fehlt die Datei oder fehlen Leserechte, landet die Fehlermeldung an Ausgang 2. Der Pfad bezieht sich auf das Dateisystem der Node-RED-Laufzeit; der Dateizugriff erfolgt mit den Rechten des Benutzers, unter dem der Service läuft.
Die Rückgabe am Ende sendet hier keine Nachricht. Erst der Callback kennt das Ergebnis und verschickt es mit node.send(). Ein return msg; innerhalb des Callbacks würde nur diesen Callback beenden. Ein zusätzliches return msg; außerhalb könnte dagegen die ursprüngliche Nachricht vorzeitig ausgeben. node.send() unterstützt dieselbe Ausgangszuordnung per Array wie die synchrone Rückgabe.
In diesem Beispiel werden Fehler bewusst über Ausgang 2 behandelt. Soll stattdessen eine Catch Node reagieren, kann der Fehlerpfad node.error(err, msg); aufrufen und die Verarbeitung anschließend mit node.done(); abschließen. Für einfache Dateioperationen bleibt eine vorhandene Datei-Node ebenfalls eine passende Lösung.
Verzögerte Ereignisse mit node.send() ausgeben
Ein Timer zeigt dasselbe Prinzip ohne zusätzliches Modul. Jede eingehende Nachricht löst eine Ausgabe aus, die eine Sekunde später am einzigen Ausgang erscheint:
setTimeout(() => {
msg.payload = "Verzögertes Ereignis";
node.send(msg);
node.done();
}, 1000);
return;
node.send() erzeugt selbst keine Asynchronität. Der Timer bestimmt den Zeitpunkt, die Methode übernimmt die Ausgabe. Jede Eingangsnachricht startet in diesem Beispiel einen eigenen Timer. Bei zwei konfigurierten Ausgängen könnte der Callback beispielsweise mit node.send([null, msg]); gezielt Ausgang 2 ansprechen. Noch offene Timer der Function Node werden beim Stoppen oder erneuten Bereitstellen aufgeräumt.
Zustand und Lebenszyklus der Function Node
Nicht jeder Wert gehört in die aktuelle Nachricht. Für Daten, die zwischen mehreren Aufrufen erhalten bleiben sollen, stehen context für die einzelne Node, flow für den Flow-Tab und global für die gesamte Laufzeit zur Verfügung. Ob diese Werte einen Neustart überstehen, hängt vom eingerichteten Context-Speicher ab.
Vorbereitungen für die Verarbeitung gehören in den Reiter Start. Im Reiter Stopp werden selbst angelegte Verbindungen oder Ereignis-Listener wieder geschlossen beziehungsweise entfernt. Das verhindert, dass nach einem erneuten Bereitstellen noch alte Verbindungen bestehen.
Für die Fehlersuche helfen Meldungen mit node.warn() und node.error(). Wie sich diese gezielt einsetzen lassen, beschreibe ich im Beitrag Node-RED Debugging ohne Debug Node: Meldungen direkt aus der Function Node. Über node.status() lässt sich außerdem ein kurzer Zustand direkt unter der Node anzeigen.
Fazit
Die Function Node ergänzt einen Flow um eigene Verarbeitung, mehrere Ausgabewege und den Zugriff auf bereitgestellte Node.js-Module. Für sofort verfügbare Ergebnisse genügt return. Bei einem später eintreffenden Ergebnis übernehmen node.send() und node.done() die Ausgabe und den Abschluss.
Der Vergleich mit der Change Node bleibt dabei hilfreich: Eine einfache Wertänderung benötigt noch keinen eigenen Code. Mit Modulzugriffen oder mehreren zusammengehörenden Verarbeitungsschritten kann eine Function Node die passende Ergänzung sein. Entscheidend ist, ob der konkrete Flow damit nachvollziehbar bleibt.
Weiterführende Dokumentation: Function Nodes in Node-RED, Node.js-Modul os und Node.js-Modul fs.
Letzte Aktualisierung am: 31. Oktober 2026








