Automation beginner ⏱ 45 minutes

Node-RED: Flow-Based Programming for IoT and Automation

Visual programming for IoT devices and automation. Connect hardware, APIs, and services with drag-and-drop.

By homelabbing.io • 2025-12-16
#node-red#iot#automation#flow-based#visual-programming

Overview

Node-RED is a flow-based development tool originally created by IBM for wiring together hardware devices, APIs, and online services. Built on Node.js, it provides a browser-based editor that makes it easy to wire together flows using a wide range of nodes from its extensive palette.

For homelabbers, Node-RED is the visual programming powerhouse that connects everything: IoT sensors talk to databases, Home Assistant triggers webhooks, MQTT messages transform and route to multiple destinations, and complex automations that would take hundreds of lines of code become drag-and-drop flows.

What you’ll learn:

  • Installing Node-RED in Docker and standalone modes
  • Understanding flows, nodes, and message passing
  • Integrating with Home Assistant for advanced automation
  • Working with MQTT for IoT device communication
  • Creating custom dashboards with Node-RED Dashboard
  • Installing and using community-contributed nodes
  • Building real-world automation workflows
  • Security best practices and production deployment

Why Node-RED for Homelabs?

Instead of writing code to poll an API, parse JSON, filter data, store to database, and trigger notifications, Node-RED lets you drag nodes onto a canvas, wire them together visually, and deploy with one click. Non-programmers can build sophisticated automations, while developers can inject JavaScript for complex logic when needed.


Prerequisites

Before starting, ensure you have:

  • Docker installed (recommended) or Node.js 18+ for standalone installation
  • Basic understanding of JSON and HTTP concepts
  • Hardware requirements:
    • 512 MB RAM minimum (1 GB recommended)
    • 1 GB storage for Node-RED and custom nodes
    • Network access on port 1880
  • Web browser (Chrome, Firefox, Edge, Safari)
  • Optional IoT devices to integrate (not required for learning)

Helpful but not required:

  • Home Assistant installation
  • MQTT broker (Mosquitto)
  • Basic JavaScript knowledge

Understanding Flow-Based Programming

The Visual Paradigm

Traditional programming is text-based and linear. Flow-based programming is visual and event-driven:

Traditional approach (Python):

import requests
import json

response = requests.get('https://api.weather.com/current')
data = json.loads(response.text)
if data['temperature'] > 30:
    send_notification('It is hot outside!')

Node-RED approach:

  1. Drag “HTTP Request” node onto canvas
  2. Drag “JSON” parser node
  3. Drag “Switch” node (if temp > 30)
  4. Drag “Notification” node
  5. Wire them together
  6. Click Deploy

Core Concepts

Nodes:

  • Functional units that process messages
  • Input nodes (inject, MQTT in, HTTP in)
  • Function nodes (transform, filter, logic)
  • Output nodes (debug, MQTT out, HTTP response)

Flows:

  • Canvas where nodes are arranged
  • Can have multiple flows (tabs) per project
  • Flows can call each other via “link” nodes

Messages (msg):

  • Data passed between nodes as JavaScript objects
  • Always has msg.payload (main data)
  • Can have additional properties (msg.topic, msg.headers, custom properties)

Message Passing:

// Message structure
{
  "payload": "Hello World",    // Main data
  "topic": "sensor/temperature",
  "_msgid": "abc123",          // Unique message ID
  "custom_property": "value"   // Your custom data
}

Flow Execution Model

Event-Driven:

  • Nodes trigger when they receive input
  • No polling loops (unlike traditional code)
  • Asynchronous by default

Example flow:

[Inject every 5min] → [HTTP Request: Weather API] → [JSON Parse] → [Function: Extract Temp] → [Debug]

Execution:

  1. Inject fires every 5 minutes
  2. HTTP Request fetches data
  3. JSON parses response
  4. Function extracts temperature
  5. Debug displays result

Installation and Initial Setup

Docker provides isolation, easy updates, and portability.

Basic Docker run:

# Create data directory
mkdir -p ~/node-red-data

# Run Node-RED container
docker run -d   --name node-red   --restart unless-stopped   -p 1880:1880   -v ~/node-red-data:/data   -e TZ=America/New_York   nodered/node-red:latest

Docker Compose (better):

Create docker-compose.yml:

version: '3.8'

services:
  node-red:
    image: nodered/node-red:latest
    container_name: node-red
    restart: unless-stopped
    ports:
      - "1880:1880"
    volumes:
      - ./node-red-data:/data
    environment:
      - TZ=America/New_York
    networks:
      - homelab

networks:
  homelab:
    driver: bridge

Deploy:

# Start Node-RED
docker-compose up -d

# View logs
docker-compose logs -f node-red

# Stop
docker-compose down

Access Node-RED:

Navigate to http://localhost:1880 or http://YOUR-SERVER-IP:1880

Method 2: Standalone Installation (Linux/macOS)

Install Node.js:

# Ubuntu/Debian
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs

# macOS
brew install node

# Verify
node --version  # Should be v18+
npm --version

Install Node-RED:

# Install globally
sudo npm install -g --unsafe-perm node-red

# Create service (systemd)
sudo wget https://raw.githubusercontent.com/node-red/linux-installers/master/resources/nodered.service -O /etc/systemd/system/nodered.service

# Enable and start
sudo systemctl enable nodered.service
sudo systemctl start nodered.service

# Check status
sudo systemctl status nodered.service

Access Node-RED:

Navigate to http://localhost:1880

Method 3: Home Assistant Add-on

If you run Home Assistant OS:

  1. Settings → Add-ons → Add-on Store
  2. Search “Node-RED”
  3. Click Install
  4. Configure (set password)
  5. Start add-on
  6. Click “Open Web UI”

Initial Configuration

On first launch:

  1. Editor opens at http://<ip>:1880
  2. Welcome screen appears (optional tour)
  3. Default flow with help nodes visible
  4. Palette on left (available nodes)
  5. Deploy button top-right (deploys changes)

Configure settings:

Click hamburger menu (☰) → Settings:

  • General: Flow file name, timeout settings
  • Security: Enable authentication (recommended)
  • Node settings: Module paths
  • Editor: Theme, syntax highlighting

Your First Node-RED Flow

Building a “Hello World” Flow

Let’s create a simple flow that displays a message.

Step 1: Add Inject Node

  1. From palette (left), drag “inject” node onto canvas
  2. Double-click to configure:
    • Name: “Say Hello”
    • msg.payload: string, “Hello, Node-RED!”
    • Topic: (leave blank)
  3. Click Done

Step 2: Add Debug Node

  1. Drag “debug” node onto canvas
  2. Configuration:
    • Output: msg.payload (default)
    • To: debug window (default)
  3. Click Done

Step 3: Wire Nodes

  1. Click output (right side) of inject node
  2. Drag to input (left side) of debug node
  3. Connection appears

Step 4: Deploy and Test

  1. Click Deploy (top-right)
  2. Open Debug panel (bug icon on right sidebar)
  3. Click button on inject node
  4. See “Hello, Node-RED!” in debug panel

Congratulations! You’ve created your first flow.

Understanding the Debug Panel

The debug panel shows:

  • Message timestamp
  • Source node
  • Message content (msg.payload by default)
  • Message ID

Debug node options:

  • Output complete msg object (not just payload)
  • Output to system console
  • Filter by flow/node
  • Search debug messages

Adding Logic with Function Nodes

Let’s modify the flow to transform data.

Add function node between inject and debug:

  1. Drag “function” node
  2. Wire: inject → function → debug
  3. Double-click function node
  4. Enter code:
// Get current timestamp
let now = new Date();

// Create new message
msg.payload = {
    message: "Hello at " + now.toLocaleTimeString(),
    temperature: 72,
    humidity: 45
};

return msg;
  1. Click Done
  2. Deploy
  3. Click inject button
  4. Debug shows object with message, temperature, humidity

Understanding function nodes:

  • Receive msg object as input
  • Can modify msg properties
  • Must return msg; to pass to next node
  • Can access Node.js modules
  • Can return multiple messages: return [msg1, msg2];

Home Assistant Integration

Setting Up the Connection

Node-RED and Home Assistant work beautifully together via the official integration.

Install Home Assistant nodes:

  1. Click hamburger menu → Manage palette
  2. Click Install tab
  3. Search “node-red-contrib-home-assistant-websocket”
  4. Click Install
  5. Wait for installation
  6. Close palette manager

Create Home Assistant server connection:

  1. Drag “events: state” node (from Home Assistant palette)
  2. Double-click to configure
  3. Click Add new server (pencil icon)
  4. Configure:
    • Name: Home Assistant
    • URL: http://homeassistant.local:8123 (or IP)
    • Access Token: (create in Home Assistant)
  5. Click Add

Get Home Assistant access token:

  1. In Home Assistant, go to Profile (bottom-left)
  2. Scroll to Long-Lived Access Tokens
  3. Click Create Token
  4. Name: “Node-RED”
  5. Copy token
  6. Paste into Node-RED server config

Automating Lights Based on Motion

Goal: Turn on kitchen light when motion detected, off after 5 minutes.

Flow design:

[Motion Sensor State] → [Switch: if ON] → [Call Service: Turn On Light] → [Delay 5min] → [Call Service: Turn Off Light]

Build the flow:

1. Add motion sensor trigger:

  1. Drag “events: state” node
  2. Configure:
    • Server: Home Assistant
    • Entity ID: binary_sensor.kitchen_motion
    • State Type: String
    • If State: on

2. Add turn-on service call:

  1. Drag “call service” node
  2. Configure:
    • Server: Home Assistant
    • Domain: light
    • Service: turn_on
    • Entity ID: light.kitchen_ceiling

3. Add delay:

  1. Drag “delay” node
  2. Configure:
    • Action: Delay each message
    • For: 5 minutes

4. Add turn-off service call:

  1. Drag “call service” node
  2. Configure:
    • Domain: light
    • Service: turn_off
    • Entity ID: light.kitchen_ceiling

5. Wire together:

Motion → Turn On → Delay → Turn Off

6. Deploy and test

Walk into kitchen, light turns on, waits 5 minutes, turns off.

Getting Entity States

Read current temperature:

  1. Drag “get entities” node
  2. Configure:
    • Entity ID: sensor.bedroom_temperature
  3. Drag “function” node
  4. Enter:
msg.payload = {
    temperature: msg.data.state,
    unit: msg.data.attributes.unit_of_measurement,
    friendly_name: msg.data.attributes.friendly_name
};
return msg;
  1. Wire: Get Entity → Function → Debug
  2. Inject (use inject node or trigger manually)

Conditional Logic Based on State

Only run automation if someone is home:

[Trigger] → [Current State: person.john] → [Switch] → [Action if home]
                                                  ↓
                                         [Action if away]

Implementation:

  1. Current state node checks person.john
  2. Switch node routes based on state:
    • If state == “home”: output 1
    • If state == “away”: output 2
  3. Wire output 1 to “home” actions
  4. Wire output 2 to “away” actions

MQTT Integration for IoT

Setting Up MQTT Broker

Install Mosquitto (if not already running):

Docker Compose:

version: '3.8'

services:
  mosquitto:
    image: eclipse-mosquitto:latest
    container_name: mosquitto
    restart: unless-stopped
    ports:
      - "1883:1883"
      - "9001:9001"
    volumes:
      - ./mosquitto/config:/mosquitto/config
      - ./mosquitto/data:/mosquitto/data
      - ./mosquitto/log:/mosquitto/log

Create config file (mosquitto/config/mosquitto.conf):

listener 1883
allow_anonymous true
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log

Start:

docker-compose up -d mosquitto

Connecting Node-RED to MQTT

Add MQTT nodes:

  1. Drag “mqtt in” node (for subscribing)
  2. Double-click, click Add broker (pencil icon)
  3. Configure:
    • Server: mosquitto (or IP: 192.168.1.100)
    • Port: 1883
    • Client ID: (auto-generated)
    • Keep Alive: 60
  4. Click Add
  5. Set Topic: sensors/temperature
  6. Click Done

Test MQTT reception:

  1. Wire MQTT in → Debug
  2. Deploy
  3. Publish test message:
# From command line
docker exec -it mosquitto mosquitto_pub -h localhost -t sensors/temperature -m '{"temp": 72.5, "unit": "F"}'
  1. Debug panel shows message

Publishing MQTT Messages

Send temperature reading:

[Inject] → [Function] → [MQTT Out]

Function node code:

msg.payload = {
    temperature: 72.5,
    humidity: 45,
    timestamp: new Date().toISOString()
};

msg.topic = "homelab/sensors/bedroom";

return msg;

MQTT out node:

  • Topic: (leave blank, using msg.topic)
  • Broker: Same broker as MQTT in

Real-World IoT Flow: Temperature Monitoring

Goal: Read temperature from MQTT, store in context, send alert if > 80F.

[MQTT In] → [JSON Parse] → [Function: Store & Check] → [Switch: if > 80] → [Notification]

Function code:

// Parse temperature
let temp = msg.payload.temperature;

// Store in flow context
flow.set("last_temperature", temp);
flow.set("last_update", new Date().toISOString());

// Check threshold
if (temp > 80) {
    msg.alert = true;
    msg.payload = `Temperature is ${temp}F - above threshold!`;
} else {
    msg.alert = false;
}

return msg;

Switch node:

  • Property: msg.alert
  • == true → output 1 (send notification)
  • == false → (do nothing)

Node-RED Dashboard

Installing Dashboard Nodes

  1. Menu → Manage palette → Install
  2. Search “node-red-dashboard”
  3. Install
  4. Dashboard nodes appear in palette

Creating a Simple Dashboard

Add dashboard elements:

1. Temperature gauge:

  1. Drag “gauge” node
  2. Configure:
    • Group: Add new group “Living Room”
    • Label: Temperature
    • Units: °F
    • Range: 0 to 100
    • Value format: {{value}}

2. Feed temperature data:

[MQTT In: temp sensor] → [JSON] → [Gauge]

3. Add button:

  1. Drag “button” node
  2. Configure:
    • Group: Living Room
    • Label: Toggle Light
    • Payload: toggle

Wire to Home Assistant call service node.

4. Add chart:

  1. Drag “chart” node
  2. Configure:
    • Group: Living Room
    • Type: Line chart
    • X-axis: Last 1 hour
    • Legend: Show

Wire temperature data to chart.

Access dashboard:

Navigate to http://<node-red-ip>:1880/ui

Dashboard Layout and Groups

Layout structure:

  • Tabs (pages): Home, Sensors, Controls
  • Groups (sections): Living Room, Bedroom, Outdoor
  • Widgets (components): Gauge, Chart, Button, Switch

Configure layout:

  1. Menu → Dashboard
  2. Click Layout tab
  3. Drag groups to reorder
  4. Set column width per group
  5. Click Deploy

Dashboard Customization

Custom themes:

// In settings.js or via UI
ui: {
    path: "ui",
    theme: {
        name: "theme-dark",
        lightTheme: {
            default: "#0094CE",
            baseColor: "#0094CE",
            baseFont: "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
        },
        darkTheme: {
            default: "#097479",
            baseColor: "#097479",
            baseFont: "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
        },
    },
}

Advanced Node Patterns

Using Context for State Storage

Context types:

  • Global: Shared across all flows
  • Flow: Shared within one flow
  • Node: Local to one function node

Example: Track motion events:

// Get context
let motionCount = flow.get("motion_count") || 0;
let lastMotion = flow.get("last_motion_time") || null;

// Increment
motionCount++;

// Store
flow.set("motion_count", motionCount);
flow.set("last_motion_time", new Date().toISOString());

msg.payload = {
    count: motionCount,
    lastMotion: lastMotion
};

return msg;

Global context for cross-flow communication:

// Flow 1: Store data
global.set("system_status", "operational");

// Flow 2: Read data
let status = global.get("system_status");
msg.payload = status;
return msg;

Problem: Large flows become messy

Solution: Use link nodes to connect across flows

Link Out (sends):

  1. Drag “link out” node to end of flow
  2. Name: “To Processing”

Link In (receives):

  1. Drag “link in” node to start of another flow
  2. Click “Add link” and select “To Processing”
  3. Wire onwards

Benefits:

  • Clean organization
  • Reusable sub-flows
  • Separate concerns

Custom Nodes from Community

Popular community nodes:

node-red-contrib-bigtimer:

  • Advanced scheduling
  • Sunrise/sunset aware
  • Cron-like syntax

node-red-contrib-calc:

  • Mathematical operations
  • Statistical functions

node-red-contrib-telegrambot:

  • Send/receive Telegram messages
  • Bot commands

Installation:

Menu → Manage palette → Install → Search → Install

Error Handling

Catch node:

[Any Node] --error--> [Catch] → [Debug/Notification]

Catch node setup:

  1. Drag “catch” node
  2. Configure:
    • Scope: All nodes (or specific)
  3. Wire to debug or notification

Function with try/catch:

try {
    let data = JSON.parse(msg.payload);
    msg.payload = data.temperature;
    return msg;
} catch (error) {
    msg.payload = "Parse error: " + error.message;
    msg.error = true;
    return msg;
}

Security and Production Deployment

Enabling Authentication

Edit settings.js:

// Generate password hash
node-red-admin hash-pw

// Add to settings.js
adminAuth: {
    type: "credentials",
    users: [{
        username: "admin",
        password: "$2b$08$...<generated-hash>...",
        permissions: "*"
    }, {
        username: "viewer",
        password: "$2b$08$...<hash>...",
        permissions: "read"
    }]
}

Restart Node-RED:

docker-compose restart node-red

HTTPS with Reverse Proxy

Nginx reverse proxy:

server {
    listen 443 ssl;
    server_name nodered.homelab.local;

    ssl_certificate /etc/nginx/certs/cert.pem;
    ssl_certificate_key /etc/nginx/certs/key.pem;

    location / {
        proxy_pass http://node-red:1880;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Backup and Version Control

Backup flows:

  1. Menu → Export → All flows
  2. Save to file
  3. Store in Git repository

Automated backup flow:

[Inject: daily] → [Exec: copy flows] → [Email backup notification]

Git integration:

# Initialize Git in data directory
cd ~/node-red-data
git init
git add flows.json flows_cred.json
git commit -m "Initial flows"

# Daily backup script
0 2 * * * cd ~/node-red-data && git add -A && git commit -m "Auto backup $(date)" && git push

Real-World Use Cases

Use Case 1: Energy Monitoring Dashboard

Components:

  • MQTT in: Power consumption data
  • Chart: Historical power usage
  • Gauge: Current consumption
  • Switch: Control high-power devices
  • Notification: Alert if usage > threshold

Use Case 2: Multi-Zone Climate Control

Flow:

  1. Read temperature from multiple sensors
  2. Calculate average per zone
  3. Compare to target temperature
  4. Send commands to thermostats/HVAC
  5. Log to database (InfluxDB)
  6. Display on dashboard

Use Case 3: Security Camera Integration

Components:

  • HTTP request: Fetch camera snapshot
  • Image node: Display on dashboard
  • Motion detection: Alert when motion seen
  • Telegram bot: Send snapshot on alert

Use Case 4: Automated Plant Watering

Flow:

  1. Soil moisture sensor → MQTT
  2. Check if moisture is below threshold
  3. Check time (only water morning/evening)
  4. Trigger relay (water pump)
  5. Run for 30 seconds
  6. Log watering event

Troubleshooting Common Issues

Flows Not Deploying

Symptom: Click Deploy, but changes don’t apply

Solutions:

  • Check for error messages in debug panel
  • Look for red triangle on nodes (configuration error)
  • Check Node-RED logs: docker logs node-red
  • Verify no syntax errors in function nodes

MQTT Connection Failing

Symptom: MQTT in/out nodes show “disconnected”

Solutions:

# Check Mosquitto is running
docker ps | grep mosquitto

# Test MQTT connection
docker exec -it mosquitto mosquitto_sub -h localhost -t '#' -v

# Verify broker settings in Node-RED
# Check IP address, port, credentials

Home Assistant Nodes Not Working

Symptom: Can’t fetch entities or call services

Solutions:

  • Verify access token is valid
  • Check Home Assistant URL is correct
  • Ensure Home Assistant is accessible from Node-RED
  • Check Home Assistant logs for authentication errors

Conclusion and Next Steps

Node-RED transforms complex automation into visual flows, making IoT integration and home automation accessible to everyone. You’ve learned:

  • Flow-based programming concepts
  • Building flows with nodes and wiring
  • Home Assistant integration
  • MQTT for IoT communication
  • Creating dashboards
  • Advanced patterns and best practices

Key Takeaways

  • Visual programming eliminates boilerplate code
  • Event-driven architecture scales easily
  • Extensive node ecosystem covers most use cases
  • Perfect companion to Home Assistant for complex automations
  • MQTT integration makes IoT device management simple
  1. Build your first automation connecting real devices
  2. Create a dashboard for home monitoring
  3. Integrate with Home Assistant for unified control
  4. Explore community nodes for advanced functionality
  5. Join Node-RED community for support and inspiration
  • Home Assistant - Smart home platform integration
  • MQTT Broker Setup - Deep dive into Mosquitto
  • InfluxDB & Grafana - Time-series data visualization
  • Zigbee2MQTT - IoT device integration

Resources


Document Version: 1.0 Last Updated: December 16, 2025 Based on: Node-RED 3.1.x Estimated Completion Time: 75 minutes

Happy flowing!