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.
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:
- Drag “HTTP Request” node onto canvas
- Drag “JSON” parser node
- Drag “Switch” node (if temp > 30)
- Drag “Notification” node
- Wire them together
- 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:
- Inject fires every 5 minutes
- HTTP Request fetches data
- JSON parses response
- Function extracts temperature
- Debug displays result
Installation and Initial Setup
Method 1: Docker (Recommended)
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:
- Settings → Add-ons → Add-on Store
- Search “Node-RED”
- Click Install
- Configure (set password)
- Start add-on
- Click “Open Web UI”
Initial Configuration
On first launch:
- Editor opens at
http://<ip>:1880 - Welcome screen appears (optional tour)
- Default flow with help nodes visible
- Palette on left (available nodes)
- 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
- From palette (left), drag “inject” node onto canvas
- Double-click to configure:
- Name: “Say Hello”
- msg.payload: string, “Hello, Node-RED!”
- Topic: (leave blank)
- Click Done
Step 2: Add Debug Node
- Drag “debug” node onto canvas
- Configuration:
- Output: msg.payload (default)
- To: debug window (default)
- Click Done
Step 3: Wire Nodes
- Click output (right side) of inject node
- Drag to input (left side) of debug node
- Connection appears
Step 4: Deploy and Test
- Click Deploy (top-right)
- Open Debug panel (bug icon on right sidebar)
- Click button on inject node
- 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:
- Drag “function” node
- Wire: inject → function → debug
- Double-click function node
- 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; - Click Done
- Deploy
- Click inject button
- Debug shows object with message, temperature, humidity
Understanding function nodes:
- Receive
msgobject 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:
- Click hamburger menu → Manage palette
- Click Install tab
- Search “node-red-contrib-home-assistant-websocket”
- Click Install
- Wait for installation
- Close palette manager
Create Home Assistant server connection:
- Drag “events: state” node (from Home Assistant palette)
- Double-click to configure
- Click Add new server (pencil icon)
- Configure:
- Name: Home Assistant
- URL:
http://homeassistant.local:8123(or IP) - Access Token: (create in Home Assistant)
- Click Add
Get Home Assistant access token:
- In Home Assistant, go to Profile (bottom-left)
- Scroll to Long-Lived Access Tokens
- Click Create Token
- Name: “Node-RED”
- Copy token
- 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:
- Drag “events: state” node
- Configure:
- Server: Home Assistant
- Entity ID:
binary_sensor.kitchen_motion - State Type: String
- If State:
on
2. Add turn-on service call:
- Drag “call service” node
- Configure:
- Server: Home Assistant
- Domain: light
- Service: turn_on
- Entity ID:
light.kitchen_ceiling
3. Add delay:
- Drag “delay” node
- Configure:
- Action: Delay each message
- For: 5 minutes
4. Add turn-off service call:
- Drag “call service” node
- 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:
- Drag “get entities” node
- Configure:
- Entity ID:
sensor.bedroom_temperature
- Entity ID:
- Drag “function” node
- Enter:
msg.payload = {
temperature: msg.data.state,
unit: msg.data.attributes.unit_of_measurement,
friendly_name: msg.data.attributes.friendly_name
};
return msg; - Wire: Get Entity → Function → Debug
- 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:
- Current state node checks
person.john - Switch node routes based on state:
- If state == “home”: output 1
- If state == “away”: output 2
- Wire output 1 to “home” actions
- 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:
- Drag “mqtt in” node (for subscribing)
- Double-click, click Add broker (pencil icon)
- Configure:
- Server:
mosquitto(or IP:192.168.1.100) - Port: 1883
- Client ID: (auto-generated)
- Keep Alive: 60
- Server:
- Click Add
- Set Topic:
sensors/temperature - Click Done
Test MQTT reception:
- Wire MQTT in → Debug
- Deploy
- Publish test message:
# From command line
docker exec -it mosquitto mosquitto_pub -h localhost -t sensors/temperature -m '{"temp": 72.5, "unit": "F"}' - 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
- Menu → Manage palette → Install
- Search “node-red-dashboard”
- Install
- Dashboard nodes appear in palette
Creating a Simple Dashboard
Add dashboard elements:
1. Temperature gauge:
- Drag “gauge” node
- 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:
- Drag “button” node
- Configure:
- Group: Living Room
- Label: Toggle Light
- Payload: toggle
Wire to Home Assistant call service node.
4. Add chart:
- Drag “chart” node
- 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:
- Menu → Dashboard
- Click Layout tab
- Drag groups to reorder
- Set column width per group
- 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; Link Nodes for Flow Organization
Problem: Large flows become messy
Solution: Use link nodes to connect across flows
Link Out (sends):
- Drag “link out” node to end of flow
- Name: “To Processing”
Link In (receives):
- Drag “link in” node to start of another flow
- Click “Add link” and select “To Processing”
- 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:
- Drag “catch” node
- Configure:
- Scope: All nodes (or specific)
- 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:
- Menu → Export → All flows
- Save to file
- 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:
- Read temperature from multiple sensors
- Calculate average per zone
- Compare to target temperature
- Send commands to thermostats/HVAC
- Log to database (InfluxDB)
- 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:
- Soil moisture sensor → MQTT
- Check if moisture is below threshold
- Check time (only water morning/evening)
- Trigger relay (water pump)
- Run for 30 seconds
- 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
Recommended Next Steps
- Build your first automation connecting real devices
- Create a dashboard for home monitoring
- Integrate with Home Assistant for unified control
- Explore community nodes for advanced functionality
- Join Node-RED community for support and inspiration
Related Guides
- Home Assistant - Smart home platform integration
- MQTT Broker Setup - Deep dive into Mosquitto
- InfluxDB & Grafana - Time-series data visualization
- Zigbee2MQTT - IoT device integration
Resources
- Official Documentation: nodered.org/docs
- Flow Library: flows.nodered.org
- Community Forum: discourse.nodered.org
- GitHub: github.com/node-red
Document Version: 1.0 Last Updated: December 16, 2025 Based on: Node-RED 3.1.x Estimated Completion Time: 75 minutes
Happy flowing!