Dashboard & Cloud

Node-RED Advanced Flow Programming

Tutorial lanjutan Node-RED untuk IoT. Pelajari subflows, context store, custom nodes, clustering, dan authentication untuk membangun flow yang robust dan scalable.

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.

💡 Tips

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

KategoriContoh NodeKegunaan
Inputinject, mqtt in, http in, websocket inSumber data/trigger
Outputdebug, mqtt out, http responseKirim data keluar
Functionfunction, switch, change, jsonTransformasi data
Storagefile, file in, csv, htmlI/O file
Dashboardui_gauge, ui_chart, ui_switchVisualisasi dashboard
Advancedlink, catch, status, completeFlow 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

# Subflow bisa berisi subflow lain (nested):
# Main Flow
#   ├── Subflow: Data Validator
#   │     ├── Subflow: Type Checker
#   │     └── Subflow: Range Checker
#   ├── Subflow: Alert Processor
#   │     ├── Subflow: Email Sender
#   │     └── Subflow: MQTT Publisher
#   └── Subflow: Data Storage
#         ├── Subflow: InfluxDB Writer
#         └── Subflow: File Archiver

# Tips: Hindari nesting lebih dari 3 level untuk readability

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

# Package structure:
# node-red-contrib-iot-sensor/
# ├── package.json
# ├── README.md
# ├── nodes/
# │   ├── sensor-validator.js    # Runtime logic
# │   └── sensor-validator.html  # Editor definition
# └── icons/
#     └── sensor.svg

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

PraktikDeskripsi
Gunakan SubflowsModularisasi flow untuk reuse dan maintainability
Error HandlingSelalu tambahkan Catch node di setiap flow tab
Rate LimitingGunakan delay node untuk menghindari message flood
Environment VariablesGunakan env vars, bukan hardcode nilai
Version ControlExport flow JSON dan commit ke Git
MonitoringPasang status node untuk monitoring koneksi
BackupBackup /data directory secara berkala
Resource LimitsSet functionTimeout dan debugMaxLength
⚠️ Perhatian Production

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:

Pertanyaan 1: Apa fungsi utama subflows di Node-RED?

a) Mengelola authentication
b) Membuat komponen reusable yang bisa digunakan di berbagai flow
c) Menyimpan data ke database
d) Mengelola koneksi MQTT

Pertanyaan 2: Tiga level context di Node-RED adalah...

a) local, remote, cloud
b) node, flow, global
c) public, private, protected
d) temp, cache, persistent

Pertanyaan 3: Custom node terdiri dari file apa saja?

a) Hanya file .js
b) File HTML (editor definition) dan JS (runtime logic)
c) File JSON saja
d) File Python dan YAML

Pertanyaan 4: Catch node digunakan untuk...

a) Menangkap data sensor
b) Menangkap error dari node lain di flow yang sama
c) Mengirim notifikasi
d) Menyimpan log ke file

Pertanyaan 5: Mengapa persistent context store penting di production?

a) Agar data tidak hilang saat Node-RED restart
b) Agar flow berjalan lebih cepat
c) Agar bisa diakses dari internet
d) Agar tidak perlu database
← SebelumnyaAzure IoT Hub Selanjutnya →TimescaleDB untuk IoT
🔍 Zoom
100%
🎨 Tema