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

  1. Open the Palette Manager from the top-right menu in the FlowFuse editor.
  2. Switch to the Install tab.
  3. Find the FlowFuse Hub Certified Nodes collection.
  4. Locate @flowfuse-certified-nodes/redis and 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.

  1. Drag any Redis node onto the canvas and open it
  2. Click the pencil icon next to Server to add a new connection
  3. 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
  4. 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.payload must already be a string when it reaches redis-command, put a json node (stringify mode) before it, or stringify in a change/function node.
  • Reading: the reply on msg.payload comes back as a string: put a json node (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.

Sample flow