Redis
A FlowFuse-certified package that lets you connect to Redis, store and retrieve data, publish and subscribe to messages, execute commands, and integrate Redis into your flows.
Built on ioredis, so anything ioredis supports (Cluster, Sentinel, TLS, connection strings, IORedis options objects) is supported here too.
Features
- Run any Redis command through a single configurable node
- Publish/subscribe for pub/sub messaging patterns
- Blocking list operations for building simple queues
- Run Lua scripts on the server for atomic, multi-step operations
- Inject a live Redis client into flow/global context for direct API access in function nodes
- Connections are pooled per config node, one connection is reused across every node pointed at the same server config, unless a node explicitly requests a dedicated (blocking) connection
- TLS and Redis Cluster support
The Redis node is not available by default. It is part of the FlowFuse Hub Certified Nodes paid add-on. Please contact our sales team at Contact us to learn more or to request access.
Install
- Open the Palette Manager from the top-right menu in the FlowFuse editor.
- Switch to the Install tab.
- Find the FlowFuse Hub Certified Nodes collection.
- Locate
@flowfuse-certified-nodes/redisand click Install.
Existing devices and hosted instances will not pick up newly installed nodes until they are restarted. Restart any instance you plan to use the node on after installing.
Nodes in this package
| Node | Type | Purpose |
|---|---|---|
| redis-config | config node | Holds the connection details shared by every other node |
| redis-command | request/response | Executes any Redis command (GET, SET, HMSET, SADD, ...) |
| redis-in | input | Subscribes to pub/sub channels/patterns, or performs blocking reads (BLPOP, etc.) |
| redis-out | output | Publishes messages or pushes to lists |
| redis-lua-script | request/response | Runs a Lua script on the server, optionally cached (EVALSHA) |
| redis-instance | injector | Places a live ioredis client into flow or global context |
Every node except redis-config and redis-instance sits inline in a flow: it receives a msg, talks to Redis, and sends a msg on.
Configuring the connection (redis-config)
You create one redis-config node per Redis server you connect to, then point every other node at it: you don't re-enter connection details on each node.
- Drag any Redis node onto the canvas and open it
- Click the pencil icon next to Server to add a new connection
- Fill in:
- Name — a friendly label for this connection, e.g.
Production Redis - Connection Options — either a connection string, or a JSON object of ioredis options
- Cluster — enable if connecting to a Redis Cluster
- Name — a friendly label for this connection, e.g.
- Click Add, then Done
Connection Options accepts either format, use whichever is more convenient:
redis://username:password@your-redis-host:6379/0
{
"host": "your-redis-host",
"port": 6379,
"password": "your-password",
"db": 0
}
The configuration examples in this README use localhost, 6379, and db: 0 as placeholder values for a local Redis instance. These values are provided for demonstration purposes only and should be replaced with the connection details for your own Redis server.
Every node pointed at the same redis-config node shares one underlying connection automatically. A node only opens its own dedicated connection when it needs to hold one open: redis-in does this automatically for subscribe/psubscribe/blocking commands, or you can force it with a node's Block option.
What redis-command accepts
redis-command runs any Redis command and returns the reply as msg.payload. Its inputs come from two places, and either can be fixed on the node or supplied dynamically on the incoming msg:
| Field | Node config | Incoming msg |
Meaning |
|---|---|---|---|
| Command | command |
— | The Redis command to run (set, get, hmset, sadd, ...). Always fixed on the node. |
| Key | topic |
msg.topic |
The key the command operates on. Leave the node's Topic blank to take it from msg.topic instead. |
| Params | params (+ paramsType) |
msg.payload |
The remaining arguments, as a JSON array. Leave the node's Params empty to take them from msg.payload instead. |
| Server | server |
— | Which redis-config connection to use. |
The reply is written to msg.payload; msg.topic is passed through unchanged.
Example — SET mykey "Hello there": either fix topic: mykey and params: ["Hello there"] on the node and inject any message, or leave both blank and inject {"topic": "mykey", "payload": ["Hello there"]}. The reply is OK.
Example — GET mykey: fix topic: mykey on the node (get takes no extra params, so payload can be [] or omitted). The reply is Hello there.
For the full list of commands and their arguments, see the official Redis command reference.
Working with JSON
Redis stores everything as strings. redis-command doesn't stringify or parse for you, so wrap it with a json node on the way in and out:
- Writing:
msg.payloadmust already be a string when it reachesredis-command, put ajsonnode (stringify mode) before it, or stringify in achange/functionnode. - Reading: the reply on
msg.payloadcomes back as a string: put ajsonnode (parse mode) after it to get an object back.
Pub/Sub messaging
redis-out publishes. Config: Command publish, Topic the channel to publish on (or take it from msg.topic if left blank). msg.payload is the message sent.
redis-in subscribes. Config: Command subscribe (exact channel) or psubscribe (pattern, e.g. TOPIC:*), Topic the channel/pattern. It has no fixed input, once deployed it emits one msg per received message, with msg.payload the message body and msg.topic the channel it arrived on.
Subscribing opens a dedicated blocking connection automatically (no need to set Block yourself) and stays open until the node is redeployed or removed.
Lists / queues
redis-out with Command rpush: Topic is the list key (or msg.topic), msg.payload is the value pushed.
redis-in with Command blpop: Topic is the list key, Timeout is how long to block (0 = wait forever). It emits a msg as soon as an item is available, with msg.payload the popped value, a simple queue consumer paired with the rpush producer above.
Lua scripting (redis-lua-script)
Use Lua scripts for atomic, multi-step operations that would otherwise need several round trips and risk a race between them. Keys go through KEYS[], everything else through ARGV[].
| Field | Node config | Meaning |
|---|---|---|
| Keys | keyval |
How many leading elements of msg.payload are treated as Redis keys (KEYS[1], KEYS[2], ...); the rest become ARGV[]. |
| Script | func |
The Lua source to run. |
| Stored | stored |
Cache the script on the server with SCRIPT LOAD and invoke it by SHA (EVALSHA) instead of resending the source every call, worth enabling once a script is stable. |
| Server | server |
Which redis-config connection to use. |
The script's return value becomes msg.payload.
-- keyval: 1, payload: ["key", "value"]
local foo = redis.call('SET', KEYS[1], ARGV[1])
return foo
cjson.encode/cjson.decode are available inside scripts for working with JSON payloads server-side, see the sample flow below for examples including ZADD/ZRANGE with encoded JSON members, and string manipulation with string.sub.
A more realistic example, atomically check and decrement stock, so two concurrent orders can't both succeed against the last unit:
-- keyval: 1, payload: ["inventory:product:SKU-12345", 3]
local key = KEYS[1]
local requested = tonumber(ARGV[1])
local current = tonumber(redis.call('GET', key) or "0")
if current >= requested then
redis.call('DECRBY', key, requested)
return {1, current - requested}
else
return {0, current}
end
msg.payload comes back as [1, remaining] on success or [0, available] if there wasn't enough stock, unpack it in a function node afterwards.
Direct client access (redis-instance)
For anything not covered by the other nodes (pipelines, SCAN, custom/module commands), drop a redis-instance node on the canvas. It doesn't sit inline in a flow; it just injects a ready-to-use ioredis client into context on deploy.
| Field | Node config | Meaning |
|---|---|---|
| Server | server |
Which redis-config connection to use. |
| Topic | topic |
The variable name the client is stored under in context, e.g. redis. |
| Location | location |
flow or global, where in context the client is placed. |
Deploy, then use it in any function node:
const redis = flow.get('redis'); // or global.get('redis')
const info = await redis.info();
msg.payload = info;
return msg;
Pipelining multiple writes in one round trip:
const redis = flow.get('redis');
const pipeline = redis.pipeline();
sensors.forEach(sensor => {
pipeline.set(`sensor:${sensor.id}:latest`, JSON.stringify(sensor), 'EX', 3600);
});
pipeline.exec((err, results) => {
if (err) { node.error(err, msg); return; }
msg.payload = { stored: results.length };
node.send(msg);
});
Scanning keys without blocking the server (KEYS * blocks Redis on large datasets, SCAN doesn't):
const redis = flow.get('redis');
let cursor = '0';
const keys = [];
do {
const [next, batch] = await redis.scan(cursor, 'MATCH', 'sensor:*:latest', 'COUNT', 100);
cursor = next;
keys.push(...batch);
} while (cursor !== '0');
msg.payload = keys;
return msg;
Custom/module commands (e.g. RedisJSON, RediSearch) can also be issued via redis.call('MODULE.COMMAND', ...) on the injected client.
