1. Node-RED: Refresher & Advanced Concepts
Node-RED adalah tool visual programming berbasis Node.js yang memungkinkan kamu membuat flow (alur kerja) dengan cara drag-and-drop node. Meskipun sering digunakan untuk IoT, Node-RED sangat fleksibel untuk automasi apapun — API integration, data processing, dashboard, dan banyak lagi.
Tutorial ini fokus pada fitur advanced yang membuat Node-RED layak digunakan di production: subflows untuk reusable components, context store untuk state management, custom nodes untuk logika khusus, clustering untuk high availability, dan authentication untuk keamanan.
Node-RED v3.x membawa banyak fitur baru termasuk typed environment variables, subflow instance properties, dan improved debugging. Pastikan kamu menggunakan versi terbaru.
Arsitektur Node-RED
# Komponen Node-RED Runtime:
# - Editor (browser): Visual flow editor berbasis web
# - Runtime (Node.js): Mengeksekusi flow di server
# - Flows: JSON file berisi definisi nodes dan connections
# - Registry: Pendaftaran node types dan credentials
# Flow execution model:
# Event-driven: Setiap node dipicu oleh message (msg)
# Non-blocking: Node berjalan secara async
# Message-passing: Node berkomunikasi via msg object
# Struktur msg object:
# msg.payload — data utama (bebas tipe)
# msg.topic — metadata topic (untuk MQTT/routing)
# msg._msgid — message ID unik
# msg._event — event context (untuk catch/error)
Ringkasan Node Types
| Kategori | Contoh Node | Kegunaan |
|---|---|---|
| Input | inject, mqtt in, http in, websocket in | Sumber data/trigger |
| Output | debug, mqtt out, http response | Kirim data keluar |
| Function | function, switch, change, json | Transformasi data |
| Storage | file, file in, csv, html | I/O file |
| Dashboard | ui_gauge, ui_chart, ui_switch | Visualisasi dashboard |
| Advanced | link, catch, status, complete | Flow control |
2. Subflows & Reusable Components
Subflow adalah flow yang dibungkus menjadi satu node reusable. Ini sangat penting untuk menjaga flow tetap terorganisir dan menghindari duplikasi.
Membuat Subflow
# Cara membuat subflow:
# 1. Menu → Subflows → Create Subflow
# 2. Tambahkan input/output node di subflow
# 3. Bangun logika di dalam subflow
# 4. Subflow muncul di palette sebagai node baru
# Contoh: Subflow "MQTT Alert Processor"
# Input: msg.payload (raw MQTT message)
# Processing:
# - Parse JSON
# - Cek threshold
# - Format alert message
# Output: msg.payload (formatted alert atau null)
# Subflow properties (dapat dikonfigurasi per instance):
# - environment variables (typed: str, num, bool, json, flow, global)
# - input/output count
# - icon dan category
Subflow Environment Variables
// Contoh subflow: Threshold Alert
// Environment variables:
// - threshold (number): Batas nilai
// - alertType (string): "email" atau "sms" atau "mqtt"
// - mqttTopic (string): Topic untuk publish alert
// Di dalam function node subflow:
let value = msg.payload.value;
let threshold = env.get("threshold"); // akses env var
let alertType = env.get("alertType");
if (value > threshold) {
msg.payload = {
alert: true,
type: alertType,
value: value,
threshold: threshold,
message: `Alert: ${msg.payload.device} = ${value} (threshold: ${threshold})`,
timestamp: Date.now()
};
return [msg, null]; // output 1: alert, output 2: null
} else {
return [null, msg]; // output 1: null, output 2: normal
}
Nested Subflows
3. Context Store & State Management
Node-RED memiliki sistem context yang memungkinkan penyimpanan data antara message. Ada tiga level context: node (per-node), flow (per-flow/tab), dan global (seluruh runtime).
Context Levels
// Node context — hanya bisa diakses oleh node yang sama
// Berguna untuk: state lokal node (counter, last value, accumulator)
let count = node.get("count") || 0;
count++;
node.set("count", count);
msg.payload = { count: count };
// Flow context — bisa diakses oleh semua node dalam flow yang sama
// Berguna untuk: shared state antar node dalam satu flow
flow.set("lastSensorValue", msg.payload.temperature);
let lastValue = flow.get("lastSensorValue");
// Global context — bisa diakses oleh semua node di semua flow
// Berguna untuk: konfigurasi shared, global state
global.set("config.alertThreshold", 40);
let threshold = global.get("config.alertThreshold");
// Async context access (v3+):
let value = flow.get("key", storeName, (err, val) => {
if (err) node.error(err);
else node.log("Value: " + val);
});
Persistent Context Store
# Konfigurasi persistent context di settings.js
# Default: in-memory (hilang saat restart)
# Tambahkan persistent store:
contextStorage:
default:
module: memory
persistent:
module: localfilesystem
config:
base: "context-data"
dir: "context"
cache: true # cache di memory untuk performa
# Usage:
// Simpan ke persistent store
flow.set("lastCalibration", calibrationData, "persistent");
// Baca dari persistent store
let cal = flow.get("lastCalibration", "persistent");
# Alternatif modules:
# - @node-red-contrib/thredis (Redis-backed)
# - node-red-contrib-postgresql-context (PostgreSQL-backed)
4. Membuat Custom Nodes
Untuk logika yang kompleks atau reusable, kamu bisa membuat custom nodes. Custom node terdiri dari file HTML (editor definition) dan file JS (runtime logic).
Struktur Custom Node
Node Runtime (sensor-validator.js)
// nodes/sensor-validator.js
module.exports = function(RED) {
function SensorValidatorNode(config) {
RED.nodes.createNode(this, config);
const node = this;
// Baca konfigurasi dari editor
node.sensorType = config.sensorType;
node.minValue = parseFloat(config.minValue);
node.maxValue = parseFloat(config.maxValue);
node.name = config.name;
// Handler saat menerima message
node.on('input', function(msg, send, done) {
// send() = mengirim message ke output
// done() = signal selesai processing
try {
const value = parseFloat(msg.payload.value);
if (isNaN(value)) {
node.warn("Invalid sensor value: " + msg.payload.value);
done();
return;
}
// Validasi range
if (value < node.minValue || value > node.maxValue) {
msg.payload = {
valid: false,
value: value,
reason: `Value ${value} out of range [${node.minValue}, ${node.maxValue}]`,
timestamp: Date.now()
};
// Output 2: invalid
send([null, msg]);
} else {
msg.payload.valid = true;
// Output 1: valid
send([msg, null]);
}
done();
} catch (err) {
// Error handling
done(err);
}
});
// Cleanup saat node dihapus/deploy
node.on('close', function(done) {
done();
});
}
// Register node type
RED.nodes.registerType("sensor-validator", SensorValidatorNode);
};
Node Editor (sensor-validator.html)
<!-- nodes/sensor-validator.html -->
<!-- Node definition untuk editor -->
<script type="text/javascript">
RED.nodes.registerType('sensor-validator', {
category: 'IoT', // Palette category
color: '#4CAF50', // Warna node
defaults: {
name: { value: "" }, // Nama node (opsional)
sensorType: { value: "temperature" }, // Tipe sensor
minValue: { value: "-40", required: true },
maxValue: { value: "80", required: true }
},
inputs: 1, // 1 input port
outputs: 2, // 2 output ports
icon: "sensor.svg",
label: function() { return this.name || "sensor-validator"; },
paletteLabel: "sensor validator"
});
</script>
<!-- Edit dialog -->
<script type="text/html" data-template-name="sensor-validator">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-sensorType">Sensor Type</label>
<select id="node-input-sensorType">
<option value="temperature">Temperature</option>
<option value="humidity">Humidity</option>
<option value="pressure">Pressure</option>
<option value="custom">Custom</option>
</select>
</div>
<div class="form-row">
<label for="node-input-minValue">Min Value</label>
<input type="text" id="node-input-minValue" placeholder="-40">
</div>
<div class="form-row">
<label for="node-input-maxValue">Max Value</label>
<input type="text" id="node-input-maxValue" placeholder="80">
</div>
</script>
<!-- Help text -->
<script type="text/html" data-help-name="sensor-validator">
<p>Validates sensor data against configurable min/max range.</p>
<h3>Outputs</h3>
<ol><li>Valid data</li><li>Invalid data (out of range)</li></ol>
</script>
5. Clustering & High Availability
Untuk production, Node-RED bisa dijalankan dalam mode cluster menggunakan Node.js cluster module atau external load balancer.
// settings.js — konfigurasi untuk production
module.exports = {
flowFile: 'flows.json',
credentialSecret: process.env.NODE_RED_CREDENTIAL_SECRET || "my-secret-key",
// UI & Editor
uiPort: process.env.PORT || 1880,
httpAdminRoot: '/admin', // Editor di /admin
httpNodeRoot: '/api', // API nodes di /api
ui: { path: "ui" }, // Dashboard di /ui
// Security — basicAuth untuk editor
adminAuth: {
type: "credentials",
users: [{
username: "admin",
password: "$2a$08$...", // bcrypt hashed
permissions: "*"
}]
},
// Node-level API auth
httpNodeAuth: { user: "api", pass: "$2a$08$..." },
// Logging
logging: {
console: {
level: "info",
metrics: false,
audit: true
}
},
// Context storage
contextStorage: {
default: { module: "memory" },
persistent: {
module: "localfilesystem",
config: { base: "/data/context", cache: true }
}
},
// Function timeout (prevent infinite loops)
functionTimeout: 30,
// Debug length limit
debugMaxLength: 1000,
// Diagnostics
diagnostics: { enabled: true, ui: true }
};
Docker Deployment
# docker-compose.yml — production
version: "3.8"
services:
nodered:
image: nodered/node-red:3.1
ports:
- "1880:1880"
volumes:
- nodered-data:/data
- ./settings.js:/data/settings.js
environment:
- NODE_RED_CREDENTIAL_SECRET=${CRED_SECRET}
- TZ=Asia/Jakarta
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: "0.5"
mosquitto:
image: eclipse-mosquitto:2
ports:
- "1883:1883"
volumes:
- ./mosquitto.conf:/mosquitto/config/mosquitto.conf
- mosquitto-data:/mosquitto/data
restart: unless-stopped
volumes:
nodered-data:
mosquitto-data:
6. Authentication & Authorization
Node-RED mendukung beberapa metode autentikasi: basic authentication (built-in), credentials file, OAuth, dan custom authentication plugin.
// settings.js — Authentication configurations
// 1. Basic Auth (paling sederhana)
adminAuth: {
type: "credentials",
users: [{
username: "admin",
password: bcrypt.hashSync("admin-password", 8),
permissions: "*"
}],
default: {
permissions: "read" // anonymous user bisa lihat tapi tidak edit
}
}
// 2. Multiple users dengan permission berbeda
adminAuth: {
type: "credentials",
users: [
{ username: "admin", password: "$2a$...", permissions: "*" },
{ username: "developer", password: "$2a$...", permissions: ["read", "write"] },
{ username: "viewer", password: "$2a$...", permissions: "read" }
]
}
// 3. API Key authentication untuk HTTP nodes
httpNodeAuth: {
user: "api-user",
pass: bcrypt.hashSync("api-password", 8)
}
// 4. Token-based auth (via custom plugin)
// npm install node-red-contrib-auth-oidc
adminAuth: require("node-red-contrib-auth-oidc")({
issuer: "https://auth.example.com",
client_id: "nodered",
client_secret: "secret",
allowed_users: ["admin@example.com"]
})
7. Error Handling & Debugging
Error handling yang baik sangat penting untuk production flow. Node-RED menyediakan beberapa mekanisme: catch node, status node, dan error reporting.
// Flow-level error handling dengan Catch node
// Setiap flow tab harus punya Catch node:
// - Catch: menangkap error dari semua node di tab
// - Catch (specific): menangkap error dari node tertentu
// Di function node, trigger error:
try {
let result = JSON.parse(msg.payload);
msg.payload = result;
return msg;
} catch (err) {
// Cara 1: node.error() — ditangkap oleh Catch node
node.error("Parse error: " + err.message, msg);
return null;
}
// Cara 2: Mengirim ke catch dengan msg._error
msg._error = {
message: "Custom error",
source: { id: node.id, type: node.type },
timestamp: Date.now()
};
node.send(msg); // Kirim ke output, bukan error
// Status indicator (icon di bawah node):
node.status({ fill: "green", shape: "dot", text: "connected" });
node.status({ fill: "yellow", shape: "ring", text: "reconnecting" });
node.status({ fill: "red", shape: "dot", text: "error: timeout" });
node.status({}); // Clear status
8. Best Practices & Production Tips
| Praktik | Deskripsi |
|---|---|
| Gunakan Subflows | Modularisasi flow untuk reuse dan maintainability |
| Error Handling | Selalu tambahkan Catch node di setiap flow tab |
| Rate Limiting | Gunakan delay node untuk menghindari message flood |
| Environment Variables | Gunakan env vars, bukan hardcode nilai |
| Version Control | Export flow JSON dan commit ke Git |
| Monitoring | Pasang status node untuk monitoring koneksi |
| Backup | Backup /data directory secara berkala |
| Resource Limits | Set functionTimeout dan debugMaxLength |
Nonaktifkan editor Node-RED di production (set httpAdminRoot: false) atau lindungi dengan strong authentication. Jangan pernah expose port 1880 ke internet tanpa autentikasi.
9. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Node-RED Advanced: