@gmag11/nodered-mcp-server 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +162 -0
- package/index.js +133 -0
- package/package.json +58 -0
- package/resources/skills/nodered-flow-builder/SKILL.md +659 -0
- package/resources/skills/nodered-flow-layout/SKILL.md +395 -0
- package/resources/skills/nodered-flowfuse-dashboard/SKILL.md +941 -0
- package/resources/skills/nodered-fundamentals/SKILL.md +323 -0
- package/resources/skills/nodered-jsonata/SKILL.md +1039 -0
- package/resources/skills/nodered-mustache/SKILL.md +588 -0
- package/resources/skills/nodered-node-reference/SKILL.md +1020 -0
- package/resources/skills/nodered-node-reference/examples/common.json +113 -0
- package/resources/skills/nodered-node-reference/examples/network.json +107 -0
- package/resources/skills/nodered-node-reference/examples/parser.json +147 -0
- package/resources/skills/nodered-node-reference/examples/sequence.json +141 -0
- package/resources/skills/nodered-node-reference/examples/storage.json +104 -0
- package/resources/skills/nodered-patterns/SKILL.md +414 -0
- package/resources/skills/nodered-patterns/examples/error-handler.json +72 -0
- package/resources/skills/nodered-patterns/examples/http-endpoint.json +42 -0
- package/resources/skills/nodered-patterns/examples/mqtt-subscriber.json +47 -0
- package/resources/skills/nodered-patterns/examples/timer-flow.json +50 -0
- package/resources/skills/nodered-subflows/SKILL.md +261 -0
- package/resources/skills/nodered-uibuilder/SKILL.md +500 -0
- package/src/auth/api-key-verifier.js +36 -0
- package/src/auth/composite-verifier.js +59 -0
- package/src/auth/config.js +106 -0
- package/src/auth/oauth-clients-store.js +107 -0
- package/src/auth/oauth-provider.js +149 -0
- package/src/auth/oauth-token-store.js +312 -0
- package/src/nodered/auth.js +158 -0
- package/src/nodered/client.js +199 -0
- package/src/nodered/comms-client.js +500 -0
- package/src/renderer/colors.js +161 -0
- package/src/renderer/geometry.js +115 -0
- package/src/renderer/html-builder.js +571 -0
- package/src/renderer/index.js +51 -0
- package/src/renderer/ir-builder.js +161 -0
- package/src/renderer/layout.js +126 -0
- package/src/renderer/mermaid-builder.js +109 -0
- package/src/renderer/svg-builder.js +228 -0
- package/src/schemas/responses.js +283 -0
- package/src/server.js +844 -0
- package/src/skills/loader.js +84 -0
- package/src/staging-store.js +258 -0
- package/src/tools/add-nodes-to-group.js +216 -0
- package/src/tools/connect-nodes.js +115 -0
- package/src/tools/constants.js +45 -0
- package/src/tools/create-flow.js +87 -0
- package/src/tools/create-node.js +126 -0
- package/src/tools/create-subflow-instance.js +123 -0
- package/src/tools/create-subflow.js +101 -0
- package/src/tools/delete-context.js +60 -0
- package/src/tools/delete-flow.js +81 -0
- package/src/tools/delete-group.js +116 -0
- package/src/tools/delete-node.js +73 -0
- package/src/tools/delete-subflow.js +103 -0
- package/src/tools/deploy.js +94 -0
- package/src/tools/disconnect-nodes.js +158 -0
- package/src/tools/export-flow.js +161 -0
- package/src/tools/export-subflow.js +78 -0
- package/src/tools/flow-utils.js +376 -0
- package/src/tools/get-config-nodes.js +86 -0
- package/src/tools/get-context.js +76 -0
- package/src/tools/get-flow-diagram.js +99 -0
- package/src/tools/get-flow-nodes.js +116 -0
- package/src/tools/get-flows.js +74 -0
- package/src/tools/get-node-detail.js +77 -0
- package/src/tools/get-node-type-detail.js +92 -0
- package/src/tools/get-palette-nodes.js +63 -0
- package/src/tools/get-staging-status.js +34 -0
- package/src/tools/get-subflow-detail.js +110 -0
- package/src/tools/get-subflows.js +105 -0
- package/src/tools/import-flow.js +310 -0
- package/src/tools/inject-message.js +117 -0
- package/src/tools/install-node.js +31 -0
- package/src/tools/read-debug-messages.js +155 -0
- package/src/tools/refresh-staging.js +62 -0
- package/src/tools/remove-nodes-from-group.js +162 -0
- package/src/tools/render-staging.js +69 -0
- package/src/tools/response-utils.js +42 -0
- package/src/tools/search-nodes.js +134 -0
- package/src/tools/uninstall-node.js +31 -0
- package/src/tools/update-flow.js +95 -0
- package/src/tools/update-group.js +77 -0
- package/src/tools/update-node.js +132 -0
- package/src/tools/update-subflow.js +84 -0
- package/src/transport/http.js +252 -0
- package/src/transport/stdio.js +16 -0
- package/src/transport/ws-server.js +223 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nodered-patterns
|
|
3
|
+
description: >-
|
|
4
|
+
Recipe book of common Node-RED flow patterns: HTTP endpoints, MQTT subscribers, timers,
|
|
5
|
+
message routing, data transformation, error handling, and modularization.
|
|
6
|
+
tools:
|
|
7
|
+
- create-flow
|
|
8
|
+
- create-node
|
|
9
|
+
- update-node
|
|
10
|
+
- connect-nodes
|
|
11
|
+
- disconnect-nodes
|
|
12
|
+
- import-flow
|
|
13
|
+
- export-flow
|
|
14
|
+
- inject-message
|
|
15
|
+
- read-debug-messages
|
|
16
|
+
- get-flow-diagram
|
|
17
|
+
- get-node-type-detail
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Node-RED Patterns & Recipes
|
|
21
|
+
|
|
22
|
+
Ready-to-use flow patterns. Each recipe lists the nodes, key properties, and wiring calls. Copy the pattern, adapt properties to your use case, and wire exactly as shown.
|
|
23
|
+
|
|
24
|
+
> **⚠️ Staging reminder:** All create-node/connect-nodes calls stage changes locally. After building any pattern, call `deploy()` to push to Node-RED. Then use `inject-message` + `read-debug-messages` to test. **NEVER skip deploy** — undeployed edits are not active.
|
|
25
|
+
|
|
26
|
+
> **🐛 Debug-first workflow:** When building a pattern, add a `debug` node after each processing node, deploy, inject, and verify the output format before adding the next node. This catches type mismatches and missing properties early. See `nodered-flow-builder` → "Build & Debug Step-by-Step" for the full recipe.
|
|
27
|
+
|
|
28
|
+
> **Prerequisites:** Read `nodered-fundamentals` first for core vocabulary. Use `nodered-flow-builder` for the step-by-step build workflow. See `nodered-flow-layout` for node positioning and flow arrangement rules.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## HTTP Endpoint
|
|
33
|
+
|
|
34
|
+
**Use when**: You need a REST API endpoint that receives requests and returns JSON.
|
|
35
|
+
|
|
36
|
+
**Nodes**: `http in` → `function` → `http response`
|
|
37
|
+
|
|
38
|
+
**Key properties**:
|
|
39
|
+
| Node | Property | Value |
|
|
40
|
+
|------|----------|-------|
|
|
41
|
+
| http in | `url` | `"/api/resource"` |
|
|
42
|
+
| http in | `method` | `"get"` (or post/put/delete) |
|
|
43
|
+
| function | `func` | `"msg.payload = { result: 'ok', data: msg.payload };\nreturn msg;"` |
|
|
44
|
+
| function | `outputs` | `1` |
|
|
45
|
+
| http response | `statusCode` | `200` |
|
|
46
|
+
|
|
47
|
+
**Wiring**:
|
|
48
|
+
```
|
|
49
|
+
connect-nodes(fromNodeId: "<httpInId>", outputPort: 0, toNodeId: "<functionId>")
|
|
50
|
+
connect-nodes(fromNodeId: "<functionId>", outputPort: 0, toNodeId: "<httpRespId>")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Import shortcut**: `import-flow` from `examples/http-endpoint.json`
|
|
54
|
+
|
|
55
|
+
> Set `msg.statusCode` in the function node to override the response status. Set `msg.headers` for custom headers.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## HTTP GET Request
|
|
60
|
+
|
|
61
|
+
**Use when**: You need to fetch data from an external API.
|
|
62
|
+
|
|
63
|
+
**Nodes**: `inject` → `http request` → `function` (optional) → `debug`
|
|
64
|
+
|
|
65
|
+
**Key properties**:
|
|
66
|
+
| Node | Property | Value |
|
|
67
|
+
|------|----------|-------|
|
|
68
|
+
| inject | `payloadType` | `"str"` |
|
|
69
|
+
| inject | `payload` | `""` |
|
|
70
|
+
| http request | `method` | `"GET"` |
|
|
71
|
+
| http request | `url` | `"https://api.example.com/data"` |
|
|
72
|
+
| http request | `ret` | `"obj"` (parsed JSON) |
|
|
73
|
+
| function (opt) | `func` | Extract/transform fields from response |
|
|
74
|
+
|
|
75
|
+
**Wiring**:
|
|
76
|
+
```
|
|
77
|
+
connect-nodes(fromNodeId: "<injectId>", outputPort: 0, toNodeId: "<httpReqId>")
|
|
78
|
+
connect-nodes(fromNodeId: "<httpReqId>", outputPort: 0, toNodeId: "<functionId>")
|
|
79
|
+
connect-nodes(fromNodeId: "<functionId>", outputPort: 0, toNodeId: "<debugId>")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Variations**: Replace inject with an `http in` to proxy API requests. Set `url` to `"{{msg.url}}"` for dynamic URLs. Use `ret: "txt"` for plain text responses.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## MQTT Subscriber
|
|
87
|
+
|
|
88
|
+
**Use when**: You need to receive messages from an MQTT broker and process them.
|
|
89
|
+
|
|
90
|
+
**Nodes**: `mqtt in` → `function` → `debug`
|
|
91
|
+
|
|
92
|
+
**Key properties**:
|
|
93
|
+
| Node | Property | Value |
|
|
94
|
+
|------|----------|-------|
|
|
95
|
+
| mqtt in | `topic` | `"sensors/+/temperature"` |
|
|
96
|
+
| mqtt in | `qos` | `"2"` |
|
|
97
|
+
| mqtt in | `broker` | MQTT broker config node ID |
|
|
98
|
+
| function | `func` | Parse/validate/transform payload |
|
|
99
|
+
| debug | `complete` | `"payload"` |
|
|
100
|
+
|
|
101
|
+
**Wiring**:
|
|
102
|
+
```
|
|
103
|
+
connect-nodes(fromNodeId: "<mqttInId>", outputPort: 0, toNodeId: "<functionId>")
|
|
104
|
+
connect-nodes(fromNodeId: "<functionId>", outputPort: 0, toNodeId: "<debugId>")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Import shortcut**: `import-flow` from `examples/mqtt-subscriber.json`
|
|
108
|
+
|
|
109
|
+
> The broker config node is created automatically when you set the `broker` property with a broker host/port in `create-node` properties. MQTT topics support `+` (single-level) and `#` (multi-level) wildcards.
|
|
110
|
+
|
|
111
|
+
> **⚠️ Credential privacy**: `get-config-nodes` and `get-node-detail` will NOT show MQTT credentials (username/password) — Node-RED strips them from API responses. To set credentials, use `update-node` on the mqtt-broker config node with: `properties: { credentials: { username: "user", password: "pass" } }`. You cannot read credentials back; to verify they are set, check if the broker connection succeeds.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Timer-Triggered Flow
|
|
116
|
+
|
|
117
|
+
**Use when**: You need a flow that runs on a schedule (every N seconds, cron, or once at startup).
|
|
118
|
+
|
|
119
|
+
**Nodes**: `inject` → `function` → `debug`
|
|
120
|
+
|
|
121
|
+
**Key properties**:
|
|
122
|
+
| Node | Property | Value |
|
|
123
|
+
|------|----------|-------|
|
|
124
|
+
| inject | `repeat` | `"60"` (seconds) or `""` for manual |
|
|
125
|
+
| inject | `crontab` | `"*/5 * * * *"` (cron) |
|
|
126
|
+
| inject | `once` | `true` (fire once at start) |
|
|
127
|
+
| function | `func` | Your scheduled logic |
|
|
128
|
+
|
|
129
|
+
**Wiring**:
|
|
130
|
+
```
|
|
131
|
+
connect-nodes(fromNodeId: "<injectId>", outputPort: 0, toNodeId: "<functionId>")
|
|
132
|
+
connect-nodes(fromNodeId: "<functionId>", outputPort: 0, toNodeId: "<debugId>")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Import shortcut**: `import-flow` from `examples/timer-flow.json`
|
|
136
|
+
|
|
137
|
+
**Schedule options**:
|
|
138
|
+
- **Interval**: Set `repeat` to seconds (e.g., `"300"` = every 5 min)
|
|
139
|
+
- **Cron**: Set `crontab` (e.g., `"0 8 * * 1-5"` = weekdays at 8am)
|
|
140
|
+
- **Manual only**: Leave `repeat` and `crontab` empty
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Message Routing
|
|
145
|
+
|
|
146
|
+
**Use when**: You need to route messages to different outputs based on conditions.
|
|
147
|
+
|
|
148
|
+
**Nodes**: `inject` → `switch` → [N outputs] → `debug` × N
|
|
149
|
+
|
|
150
|
+
**Key properties**:
|
|
151
|
+
| Node | Property | Value |
|
|
152
|
+
|------|----------|-------|
|
|
153
|
+
| switch | `property` | `"payload"` |
|
|
154
|
+
| switch | `rules` | Array of condition objects |
|
|
155
|
+
| switch | `checkall` | `"false"` (stop at first match) |
|
|
156
|
+
|
|
157
|
+
**Example — route by type**:
|
|
158
|
+
```javascript
|
|
159
|
+
// Switch rules:
|
|
160
|
+
rules: [
|
|
161
|
+
{ t: "eq", v: "alarm", vt: "str" }, // Output port 0
|
|
162
|
+
{ t: "eq", v: "warning", vt: "str" }, // Output port 1
|
|
163
|
+
{ t: "eq", v: "info", vt: "str" } // Output port 2
|
|
164
|
+
]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Wiring** (3 outputs, 0-indexed ports):
|
|
168
|
+
```
|
|
169
|
+
connect-nodes(fromNodeId: "<switchId>", outputPort: 0, toNodeId: "<alarmDebugId>")
|
|
170
|
+
connect-nodes(fromNodeId: "<switchId>", outputPort: 1, toNodeId: "<warnDebugId>")
|
|
171
|
+
connect-nodes(fromNodeId: "<switchId>", outputPort: 2, toNodeId: "<infoDebugId>")
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
> Each rule maps to an output port by index. Rule 0 → port 0, rule 1 → port 1, etc. Add `{ t: "else" }` as a catch-all rule.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Data Transformation
|
|
179
|
+
|
|
180
|
+
**Use when**: You need to reshape, rename, or compute message properties.
|
|
181
|
+
|
|
182
|
+
**Decision guide**: Use `change` for simple operations (set/move/delete properties). Use `function` for logic (conditions, loops, external calls).
|
|
183
|
+
|
|
184
|
+
### change node (simple)
|
|
185
|
+
| Property | Value |
|
|
186
|
+
|----------|-------|
|
|
187
|
+
| `rules` | `[{ t: "set", p: "payload.formatted", pt: "msg", to: "{{payload.name}} - {{payload.id}}", tot: "str" }]` |
|
|
188
|
+
|
|
189
|
+
### function node (complex)
|
|
190
|
+
```javascript
|
|
191
|
+
// Compute derived fields, filter, aggregate
|
|
192
|
+
if (!msg.payload.items) return null;
|
|
193
|
+
msg.payload.total = msg.payload.items.reduce((s, i) => s + i.price, 0);
|
|
194
|
+
msg.payload.count = msg.payload.items.length;
|
|
195
|
+
return msg;
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**When to use each**:
|
|
199
|
+
- **change**: Set/delete/move a property, simple Mustache formatting
|
|
200
|
+
- **function**: Conditional logic, loops, external API calls, complex math
|
|
201
|
+
- **template**: Simple text formatting with `{{ }}` placeholders
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Error Handler
|
|
206
|
+
|
|
207
|
+
**Use when**: You need to catch and handle errors from any node on a flow tab.
|
|
208
|
+
|
|
209
|
+
**Nodes**: `catch` → `function` (log/notify) → `debug`
|
|
210
|
+
|
|
211
|
+
**Key properties**:
|
|
212
|
+
| Node | Property | Value |
|
|
213
|
+
|------|----------|-------|
|
|
214
|
+
| catch | `scope` | `[]` (all nodes on tab) or `["<nodeId>", ...]` |
|
|
215
|
+
| catch | `uncaught` | `false` |
|
|
216
|
+
| function | `func` | Error processing/logging |
|
|
217
|
+
|
|
218
|
+
**Wiring**:
|
|
219
|
+
```
|
|
220
|
+
connect-nodes(fromNodeId: "<catchId>", outputPort: 0, toNodeId: "<functionId>")
|
|
221
|
+
connect-nodes(fromNodeId: "<functionId>", outputPort: 0, toNodeId: "<debugId>")
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Import shortcut**: `import-flow` from `examples/error-handler.json`
|
|
225
|
+
|
|
226
|
+
**Error message structure** (available as `msg`):
|
|
227
|
+
```javascript
|
|
228
|
+
{
|
|
229
|
+
payload: {
|
|
230
|
+
message: "Error description",
|
|
231
|
+
source: { id: "nodeId", type: "nodeType", name: "nodeName" }
|
|
232
|
+
},
|
|
233
|
+
error: { message: "...", stack: "..." }
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
> Place one catch node per tab. Scope it broadly (empty array = all nodes) unless you have specific error routing needs. Wire it to a debug node at minimum — uncaught errors are silently swallowed otherwise.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Rate Limiting
|
|
242
|
+
|
|
243
|
+
**Use when**: You need to limit the rate of messages passing through a flow (protect downstream APIs, prevent burst overload).
|
|
244
|
+
|
|
245
|
+
**Nodes**: `inject` (source) → `delay` → `function` → `debug`
|
|
246
|
+
|
|
247
|
+
**Key properties**:
|
|
248
|
+
| Node | Property | Value |
|
|
249
|
+
|------|----------|-------|
|
|
250
|
+
| delay | `pauseType` | `"rate"` |
|
|
251
|
+
| delay | `rate` | `1` (messages per window) |
|
|
252
|
+
| delay | `nbRateUnits` | `1` |
|
|
253
|
+
| delay | `rateUnits` | `"second"` |
|
|
254
|
+
| delay | `drop` | `true` (drop intermediate) or `false` (queue) |
|
|
255
|
+
|
|
256
|
+
**Wiring**: Standard sequential wiring.
|
|
257
|
+
|
|
258
|
+
**Common rate configurations**:
|
|
259
|
+
- 1 msg/sec: `rate: 1, rateUnits: "second"`
|
|
260
|
+
- 100 msgs/min: `rate: 100, nbRateUnits: 1, rateUnits: "minute"`
|
|
261
|
+
- 1 msg/sec, queue overflow: `drop: false` (messages wait in queue)
|
|
262
|
+
- 1 msg/sec, discard overflow: `drop: true` (intermediate messages lost)
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Join Streams
|
|
267
|
+
|
|
268
|
+
**Use when**: You need to combine messages from multiple sources into a single output.
|
|
269
|
+
|
|
270
|
+
**Nodes**: `inject` (2+) → ... → `join` → `function` → `debug`
|
|
271
|
+
|
|
272
|
+
**Key properties**:
|
|
273
|
+
| Node | Property | Value |
|
|
274
|
+
|------|----------|-------|
|
|
275
|
+
| join | `mode` | `"custom"` |
|
|
276
|
+
| join | `build` | `"merged"` (merge objects), `"array"` (collect), `"string"` (concatenate) |
|
|
277
|
+
| join | `key` | `"topic"` (group by topic) |
|
|
278
|
+
| join | `timeout` | `5` (seconds to auto-send partial) |
|
|
279
|
+
|
|
280
|
+
**Wiring** — all source branches converge on the single join node input:
|
|
281
|
+
```
|
|
282
|
+
connect-nodes(fromNodeId: "<source1Id>", outputPort: 0, toNodeId: "<joinId>")
|
|
283
|
+
connect-nodes(fromNodeId: "<source2Id>", outputPort: 0, toNodeId: "<joinId>")
|
|
284
|
+
connect-nodes(fromNodeId: "<joinId>", outputPort: 0, toNodeId: "<functionId>")
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
> Set the same `msg.topic` on all messages being joined. The join node groups by `key` property. Use `mode: "auto"` when paired with a split node upstream.
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Retry with Backoff
|
|
292
|
+
|
|
293
|
+
**Use when**: External API calls may fail transiently and should be retried with increasing delays.
|
|
294
|
+
|
|
295
|
+
**Nodes**: `inject` → `function` (attempt) → `switch` (check result) → output (port 0, success) OR → `delay` → loop back (port 1, retry)
|
|
296
|
+
|
|
297
|
+
**Key properties**:
|
|
298
|
+
| Node | Property | Value |
|
|
299
|
+
|------|----------|-------|
|
|
300
|
+
| function (attempt) | `func` | Attempt API call, increment retry counter |
|
|
301
|
+
| switch | `rules` | Check `msg.payload.success` or status code |
|
|
302
|
+
| delay | `pauseType` | `"delay"` |
|
|
303
|
+
| delay | `timeout` | Exponential backoff via `msg.delay` |
|
|
304
|
+
|
|
305
|
+
**Function body** (attempt node):
|
|
306
|
+
```javascript
|
|
307
|
+
msg.attempts = (msg.attempts || 0) + 1;
|
|
308
|
+
const MAX = 5;
|
|
309
|
+
// ... make API call, set msg.payload ...
|
|
310
|
+
if (!msg.payload.success && msg.attempts < MAX) {
|
|
311
|
+
msg.delay = Math.pow(2, msg.attempts) * 1000; // 2s, 4s, 8s...
|
|
312
|
+
msg.topic = "retry";
|
|
313
|
+
} else {
|
|
314
|
+
msg.topic = msg.payload.success ? "success" : "exhausted";
|
|
315
|
+
}
|
|
316
|
+
return msg;
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Wiring** — loop pattern:
|
|
320
|
+
```
|
|
321
|
+
connect-nodes(fromNodeId: "<functionId>", outputPort: 0, toNodeId: "<switchId>")
|
|
322
|
+
connect-nodes(fromNodeId: "<switchId>", outputPort: 0, toNodeId: "<successDebugId>")
|
|
323
|
+
connect-nodes(fromNodeId: "<switchId>", outputPort: 1, toNodeId: "<delayId>")
|
|
324
|
+
connect-nodes(fromNodeId: "<delayId>", outputPort: 0, toNodeId: "<functionId>") // LOOP
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
> ⚠️ Always include a max retry count to prevent infinite loops. Use exponential backoff (`Math.pow(2, n) * 1000`) to space retries.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Cross-Tab Reuse with Link Nodes
|
|
332
|
+
|
|
333
|
+
**Use when**: You need to share logic across multiple flow tabs without duplicating nodes.
|
|
334
|
+
|
|
335
|
+
**Nodes**:
|
|
336
|
+
- **Producer tab**: source → `link out`
|
|
337
|
+
- **Consumer tab**: `link in` → function → debug
|
|
338
|
+
- **Request-reply**: `link call` (producer) → ... → `link out` (response) ← `link in` (consumer request)
|
|
339
|
+
|
|
340
|
+
**Key properties**:
|
|
341
|
+
| Node | Property | Value |
|
|
342
|
+
|------|----------|-------|
|
|
343
|
+
| link out | `links` | `["shared-bus-name"]` |
|
|
344
|
+
| link in | `links` | `["shared-bus-name"]` |
|
|
345
|
+
| link call | `links` | `["shared-bus-name"]` |
|
|
346
|
+
| link call | `linkType` | `"dynamic"` |
|
|
347
|
+
|
|
348
|
+
**Pattern — pub/sub across tabs**:
|
|
349
|
+
1. Create `link out` on producer tab with `links: ["data-feed"]`
|
|
350
|
+
2. Create `link in` on each consumer tab with `links: ["data-feed"]`
|
|
351
|
+
3. All link in nodes receive every message from the link out
|
|
352
|
+
|
|
353
|
+
**Pattern — request-reply**:
|
|
354
|
+
1. Producer: `link call` with `links: ["api"]` → sends, waits for response
|
|
355
|
+
2. Consumer: `link in` with `links: ["api"]` → processes → `link out` responds
|
|
356
|
+
|
|
357
|
+
> Link nodes connect by name, not wires. No `connect-nodes` calls needed between tabs. This is the cleanest way to modularize flows without subflows.
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Dashboard / UI Patterns
|
|
362
|
+
|
|
363
|
+
Node-RED has two actively maintained options for building user interfaces and dashboards. They serve different use cases with fundamentally different architectures. This section helps you choose the right tool for the task.
|
|
364
|
+
|
|
365
|
+
### The Two Approaches
|
|
366
|
+
|
|
367
|
+
1. **`@flowfuse/node-red-dashboard` (Dashboard 2.0)** — Widget-based. Each UI element (button, chart, gauge, form) is a standard Node-RED node that you create with `create-node` and wire with `connect-nodes` on the flow canvas. Best for **quick dashboards, monitoring screens, and data visualization**.
|
|
368
|
+
|
|
369
|
+
2. **`node-red-contrib-uibuilder`** — Bridge-based. A single Node-RED node connects via Socket.IO to a full web application stored on the filesystem. You write the frontend code yourself using any framework (or none). Best for **custom web applications, complex UIs, and projects needing full frontend control**.
|
|
370
|
+
|
|
371
|
+
> **❌ Obsolete:** `node-red-dashboard` v1 (the original AngularJS-based dashboard) is deprecated. Use Dashboard 2.0 (`@flowfuse/node-red-dashboard`) for widget-based dashboards.
|
|
372
|
+
|
|
373
|
+
### Comparison Table
|
|
374
|
+
|
|
375
|
+
| Criterion | Dashboard 2.0 | UIBuilder |
|
|
376
|
+
|-----------|---------------|-----------|
|
|
377
|
+
| **Effort (simple UI)** | Very low — drag widgets, wire, deploy | Medium — write HTML/JS, manage filesystem |
|
|
378
|
+
| **Effort (complex UI)** | Medium — limited by widget catalog | High — but unlimited flexibility |
|
|
379
|
+
| **Flexibility** | Low-Medium — constrained to available widgets | Unlimited — any HTML/CSS/JS |
|
|
380
|
+
| **Real-time updates** | Built-in — `msg.payload` updates widgets | Built-in — Socket.IO bidirectional |
|
|
381
|
+
| **Custom styling** | Limited — CSS classes on widgets, `ui-template` for custom components | Full control — your own CSS/framework |
|
|
382
|
+
| **Learning curve** | Low — standard Node-RED node concepts | Medium-High — requires frontend dev skills |
|
|
383
|
+
| **Multi-user** | Built-in — `msg._client` for per-user data | Manual — implement in your frontend code |
|
|
384
|
+
| **Mobile responsive** | Built-in — Vuetify responsive grid | Manual — your own responsive design |
|
|
385
|
+
| **Best for** | IoT dashboards, monitoring, data viz, quick prototypes | Custom SPAs, branded UIs, complex workflows, web portals |
|
|
386
|
+
| **Frontend skills needed** | None (declarative nodes) | HTML + JS required; framework knowledge optional |
|
|
387
|
+
| **npm package** | `@flowfuse/node-red-dashboard` | `node-red-contrib-uibuilder` |
|
|
388
|
+
|
|
389
|
+
### Decision Guide
|
|
390
|
+
|
|
391
|
+
**Choose Dashboard 2.0 when:**
|
|
392
|
+
- You need a dashboard up quickly with minimal coding
|
|
393
|
+
- Your UI fits within the available widget types (buttons, charts, gauges, tables, forms)
|
|
394
|
+
- You want built-in responsive/mobile support
|
|
395
|
+
- Multiple users need per-user data with minimal configuration
|
|
396
|
+
- You're building monitoring screens, IoT dashboards, or data visualization
|
|
397
|
+
|
|
398
|
+
**Choose UIBuilder when:**
|
|
399
|
+
- You need a fully custom UI that doesn't fit standard widgets
|
|
400
|
+
- You want to use a specific frontend framework (Vue, React, Svelte)
|
|
401
|
+
- The UI is part of a larger web application
|
|
402
|
+
- You need complete control over styling, layout, and behavior
|
|
403
|
+
- You're building a web portal, single-page application, or branded interface
|
|
404
|
+
- You have frontend development skills on the team
|
|
405
|
+
|
|
406
|
+
### Mixing Both
|
|
407
|
+
|
|
408
|
+
Dashboard 2.0 and uibuilder can coexist in the same Node-RED instance. Use Dashboard 2.0 for internal monitoring screens and uibuilder for the customer-facing web portal — all driven by the same Node-RED flows.
|
|
409
|
+
|
|
410
|
+
### Detailed Guidance
|
|
411
|
+
|
|
412
|
+
For widget properties, wiring patterns, and recipes for Dashboard 2.0, read `flowfuse-dashboard`.
|
|
413
|
+
|
|
414
|
+
For bridge architecture, `msg._ui` protocol details, framework integration snippets, and recipes for uibuilder, read `nodered-uibuilder`.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "pat-error-tab",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "Error Handler Pattern",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "Error handling: catch node captures errors from all nodes on this tab"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "inject-pattern-error",
|
|
11
|
+
"type": "inject",
|
|
12
|
+
"z": "pat-error-tab",
|
|
13
|
+
"name": "Trigger error test",
|
|
14
|
+
"props": [{ "p": "payload" }],
|
|
15
|
+
"repeat": "",
|
|
16
|
+
"crontab": "",
|
|
17
|
+
"once": false,
|
|
18
|
+
"topic": "",
|
|
19
|
+
"payload": "",
|
|
20
|
+
"payloadType": "date",
|
|
21
|
+
"x": 100,
|
|
22
|
+
"y": 100,
|
|
23
|
+
"wires": [["func-pattern-bad"]]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "func-pattern-bad",
|
|
27
|
+
"type": "function",
|
|
28
|
+
"z": "pat-error-tab",
|
|
29
|
+
"name": "Faulty function",
|
|
30
|
+
"func": "// Simulate an error\nif (!msg.payload) {\n node.error(\"Empty payload received\", msg);\n return null;\n}\nreturn msg;",
|
|
31
|
+
"outputs": 1,
|
|
32
|
+
"x": 300,
|
|
33
|
+
"y": 100,
|
|
34
|
+
"wires": [[]]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "catch-pattern-1",
|
|
38
|
+
"type": "catch",
|
|
39
|
+
"z": "pat-error-tab",
|
|
40
|
+
"name": "Catch all errors",
|
|
41
|
+
"scope": [],
|
|
42
|
+
"uncaught": false,
|
|
43
|
+
"x": 300,
|
|
44
|
+
"y": 200,
|
|
45
|
+
"wires": [["func-pattern-handler"]]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "func-pattern-handler",
|
|
49
|
+
"type": "function",
|
|
50
|
+
"z": "pat-error-tab",
|
|
51
|
+
"name": "Log error details",
|
|
52
|
+
"func": "// Extract error info\nmsg.payload = {\n error: msg.error ? msg.error.message : \"unknown\",\n source: msg.payload.source,\n timestamp: Date.now()\n};\nreturn msg;",
|
|
53
|
+
"outputs": 1,
|
|
54
|
+
"x": 500,
|
|
55
|
+
"y": 200,
|
|
56
|
+
"wires": [["debug-pattern-error"]]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "debug-pattern-error",
|
|
60
|
+
"type": "debug",
|
|
61
|
+
"z": "pat-error-tab",
|
|
62
|
+
"name": "Error output",
|
|
63
|
+
"active": true,
|
|
64
|
+
"tosidebar": true,
|
|
65
|
+
"console": false,
|
|
66
|
+
"complete": "payload",
|
|
67
|
+
"targetType": "msg",
|
|
68
|
+
"x": 700,
|
|
69
|
+
"y": 200,
|
|
70
|
+
"wires": []
|
|
71
|
+
}
|
|
72
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "pat-http-endpoint-tab",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "HTTP Endpoint Pattern",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "HTTP endpoint: GET /api/hello → returns JSON response"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "httpin-pattern-1",
|
|
11
|
+
"type": "http in",
|
|
12
|
+
"z": "pat-http-endpoint-tab",
|
|
13
|
+
"name": "GET /api/hello",
|
|
14
|
+
"url": "/api/hello",
|
|
15
|
+
"method": "get",
|
|
16
|
+
"x": 100,
|
|
17
|
+
"y": 100,
|
|
18
|
+
"wires": [["func-pattern-api"]]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "func-pattern-api",
|
|
22
|
+
"type": "function",
|
|
23
|
+
"z": "pat-http-endpoint-tab",
|
|
24
|
+
"name": "Build JSON response",
|
|
25
|
+
"func": "msg.payload = {\n message: \"Hello from Node-RED\",\n timestamp: Date.now(),\n method: msg.req.method,\n query: msg.req.query\n};\nreturn msg;",
|
|
26
|
+
"outputs": 1,
|
|
27
|
+
"x": 300,
|
|
28
|
+
"y": 100,
|
|
29
|
+
"wires": [["httpresp-pattern-1"]]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "httpresp-pattern-1",
|
|
33
|
+
"type": "http response",
|
|
34
|
+
"z": "pat-http-endpoint-tab",
|
|
35
|
+
"name": "Send JSON response",
|
|
36
|
+
"statusCode": 200,
|
|
37
|
+
"headers": {},
|
|
38
|
+
"x": 500,
|
|
39
|
+
"y": 100,
|
|
40
|
+
"wires": []
|
|
41
|
+
}
|
|
42
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "pat-mqtt-tab",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "MQTT Subscriber Pattern",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "MQTT subscriber: receives messages, transforms payload, logs to debug"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "mqttin-pattern-1",
|
|
11
|
+
"type": "mqtt in",
|
|
12
|
+
"z": "pat-mqtt-tab",
|
|
13
|
+
"name": "Subscribe sensors/#",
|
|
14
|
+
"topic": "sensors/#",
|
|
15
|
+
"qos": "2",
|
|
16
|
+
"datatype": "auto-detect",
|
|
17
|
+
"broker": "",
|
|
18
|
+
"x": 100,
|
|
19
|
+
"y": 100,
|
|
20
|
+
"wires": [["func-pattern-mqtt"]]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "func-pattern-mqtt",
|
|
24
|
+
"type": "function",
|
|
25
|
+
"z": "pat-mqtt-tab",
|
|
26
|
+
"name": "Parse & enrich",
|
|
27
|
+
"func": "// Parse sensor data and add metadata\nconst data = typeof msg.payload === \"string\" ? JSON.parse(msg.payload) : msg.payload;\nmsg.payload = {\n device: data.device || \"unknown\",\n value: data.value,\n unit: data.unit || \"raw\",\n receivedAt: Date.now()\n};\nreturn msg;",
|
|
28
|
+
"outputs": 1,
|
|
29
|
+
"x": 300,
|
|
30
|
+
"y": 100,
|
|
31
|
+
"wires": [["debug-pattern-mqtt"]]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "debug-pattern-mqtt",
|
|
35
|
+
"type": "debug",
|
|
36
|
+
"z": "pat-mqtt-tab",
|
|
37
|
+
"name": "Show sensor data",
|
|
38
|
+
"active": true,
|
|
39
|
+
"tosidebar": true,
|
|
40
|
+
"console": false,
|
|
41
|
+
"complete": "payload",
|
|
42
|
+
"targetType": "msg",
|
|
43
|
+
"x": 500,
|
|
44
|
+
"y": 100,
|
|
45
|
+
"wires": []
|
|
46
|
+
}
|
|
47
|
+
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "pat-timer-tab",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "Timer-Triggered Flow Pattern",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "Scheduled flow: runs every 5 minutes, fetches and processes data"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "inject-pattern-timer",
|
|
11
|
+
"type": "inject",
|
|
12
|
+
"z": "pat-timer-tab",
|
|
13
|
+
"name": "Every 5 minutes",
|
|
14
|
+
"props": [{ "p": "payload" }],
|
|
15
|
+
"repeat": "300",
|
|
16
|
+
"crontab": "",
|
|
17
|
+
"once": true,
|
|
18
|
+
"topic": "",
|
|
19
|
+
"payload": "",
|
|
20
|
+
"payloadType": "date",
|
|
21
|
+
"x": 100,
|
|
22
|
+
"y": 100,
|
|
23
|
+
"wires": [["func-pattern-timer"]]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "func-pattern-timer",
|
|
27
|
+
"type": "function",
|
|
28
|
+
"z": "pat-timer-tab",
|
|
29
|
+
"name": "Scheduled task",
|
|
30
|
+
"func": "// Runs every 5 minutes\nconst now = new Date().toISOString();\nconst count = context.get(\"runCount\") || 0;\ncontext.set(\"runCount\", count + 1);\n\nmsg.payload = {\n run: count + 1,\n time: now,\n status: \"ok\"\n};\nreturn msg;",
|
|
31
|
+
"outputs": 1,
|
|
32
|
+
"x": 300,
|
|
33
|
+
"y": 100,
|
|
34
|
+
"wires": [["debug-pattern-timer"]]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "debug-pattern-timer",
|
|
38
|
+
"type": "debug",
|
|
39
|
+
"z": "pat-timer-tab",
|
|
40
|
+
"name": "Show result",
|
|
41
|
+
"active": true,
|
|
42
|
+
"tosidebar": true,
|
|
43
|
+
"console": false,
|
|
44
|
+
"complete": "payload",
|
|
45
|
+
"targetType": "msg",
|
|
46
|
+
"x": 500,
|
|
47
|
+
"y": 100,
|
|
48
|
+
"wires": []
|
|
49
|
+
}
|
|
50
|
+
]
|