Redis

Cache, sessions, rate-limit & pub/sub backbone

Redis is the second mandatory dependency. Nucleus leans on it for everything that must be fast, shared across instances, or expiring on a timer — so a single Redis instance quietly powers a surprising amount of the framework.

You can connect in one of two ways: directly over the wire with ioredis, or through a Dapr state-store component when you run inside Kubernetes. The redis block selects between them and supplies the coordinates.

Like the database, connection fields hold environment-variable names rather than literal values, so the same config travels safely between environments.

Direct connection#

The default mode. Provide either a single url or a host + port pair. Each of these is the NAME of an environment variable — Nucleus resolves process.env[...] at startup, so secrets stay out of your config file. A REDIS_PASSWORD env var is picked up automatically in host/port mode. When you connect via url, the password must be embedded in the URL (redis://:pass@host:port) — a separate REDIS_PASSWORD is ignored in url mode, because the direct client is built with new Redis(config.url) and never applies the standalone password. REDIS_AUTH_MODE (default 'password') can be set to use Azure Entra ID token-based AUTH with TLS (AZURE_CLIENT_ID as the username), with the token refreshed in the background.

Direct mode (local & most deployments)
1{2  "redis": {3    "url": "REDIS_URL",4    "withDapr": false5  }6}
urlstringOptional

Name of an env var holding a full Redis URL (redis://… or rediss://…). When set in the default password auth mode it takes precedence over host/port (the direct client is built with new Redis(config.url)). Resolved via process.env at boot. Note: when REDIS_AUTH_MODE is not 'password' (Entra ID token auth), url is ignored and only host/port are used.

Example: "REDIS_URL"

hoststringOptional

Name of an env var holding the Redis hostname. Used together with port when url is not provided. In direct mode you must supply either url or both host and port — if neither resolves, startup throws "Redis config requires either url or host and port." (there is no silent localhost fallback).

Example: "REDIS_HOST"

portstringOptional

Name of an env var holding the Redis port. Parsed to an integer at startup; an unparseable or unset value is treated as no port, so a host with no valid port fails the "url or host and port" requirement and startup throws.

Example: "REDIS_PORT"

keyPrefixstringOptional

A global prefix prepended to EVERY key this app reads or writes — sessions, roles, oauth, captcha, rate-limit, quota, monitoring, all of them. Lets many apps share one Redis instance without colliding: give each a unique prefix (e.g. "poc_ab12cd:"). Empty/unset means no prefix — existing deployments are unchanged. Like the other fields it is resolved as an env-var NAME first, then falls back to the literal string (keyPrefix "REDIS_KEY_PREFIX" uses process.env.REDIS_KEY_PREFIX if set, otherwise the literal "REDIS_KEY_PREFIX"). In direct mode it is applied by ioredis natively so it covers every command (including raw getClient() access like rate-limit and quota counters); in Dapr mode it is prepended to each state key. One caveat: the native prefix is NOT applied to KEYS/SCAN match patterns, so keys(pattern) must include the prefix in the pattern yourself.

Example: "REDIS_KEY_PREFIX"

Dapr state-store mode#

When running under Dapr (typically in Kubernetes), Nucleus talks to Redis through the Dapr sidecar's state-store building block instead of a direct socket. This decouples your service from connection management and lets the platform own credentials and TLS.

Dapr mode (Kubernetes)
1{2  "redis": {3    "withDapr": "USE_DAPR",4    "stateStoreName": "statestore"5  }6}
withDaprboolean | stringOptional

Selects the transport. A boolean is used directly. A string is treated as an env-var name and resolved at runtime — it counts as enabled unless the resolved value is "false" (case-insensitive); an unset env var counts as enabled. Handy for toggling Dapr on in the cluster and off locally from the same config.

Defaultfalse
stateStoreNamestringOptional

The name of the Dapr state-store component to bind to (e.g. "statestore"). Required whenever withDapr resolves true — startup throws if it is missing. Ignored in direct mode.

Example: "statestore"

What Redis powers#

Understanding the breadth of Redis usage helps you size and secure it correctly. A single store underpins all of the following. Keys use per-subsystem prefixes (session:, refresh:, tenant:, rl:, captcha: …) rather than an appId namespace — so give each independent service its own Redis instance or logical database rather than assuming appId-level isolation.

Under the hood — RedisManager#

Each transport is a store class — DirectRedisStore or DaprRedisStore — that implements a small IStateStore interface. One RedisManager (a process-wide singleton) holds whichever store is active and delegates its create/read/update/remove/exists to it, so the rest of the framework calls the same methods whether you run direct ioredis or the Dapr state store. RedisManager is more than a key-value wrapper — on top of that delegated CRUD it also exposes the coordination primitives Nucleus relies on (counters, locks, key scans, reauthentication) that live outside IStateStore.

create / read / update / remove / existsRedisResult<T>Optional

JSON-serialising CRUD that never throws — each returns { success, data } | { success:false, error }. create takes an optional TTL (SET … EX); in direct mode update defaults to KEEPTTL so refreshing a value preserves its remaining expiry rather than resetting it. This TTL-preservation guarantee is direct-mode only: under Dapr, update() ignores the preserveTtl flag and re-saves with no ttl metadata, so the key's remaining expiry is dropped.

acquireLock / releaseLock / waitForLockdistributed lockOptional

acquireLock uses SET key 1 EX ttl NX for an atomic, self-expiring lock in direct mode (a check-then-set fallback under Dapr). waitForLock polls until a lock frees or a timeout elapses. These guard single-flight operations across instances — most visibly refreshAccessTokenWithLock, which serialises concurrent refresh-token rotation so a burst of tabs can't double-rotate.

getOrWaitpoll-until-presentOptional

Reads a key, and if absent polls until it appears or a timeout elapses — the building block behind async hand-offs (e.g. waiting for a value another worker is about to write).

reauthenticate(username, password)rotating credsOptional

Issues a fresh AUTH on the live ioredis client without reconnecting — the Redis counterpart to the database's async password, for AAD / rotating-secret deployments.

keys(pattern)direct mode onlyOptional

Pattern scans work over a direct connection but are intentionally disabled under Dapr (the state-store building block has no KEYS); the method logs a warning and returns []. Code paths that enumerate keys therefore assume direct mode.

readCounters / incrementCounterquota / ClaimGuard countersOptional

The atomic usage-counter primitives behind quotas and ClaimGuard: incrementCounter(key, delta, ttl?) is atomic on a direct connection (INCRBYFLOAT + best-effort EXPIRE) and a best-effort read-modify-write under Dapr; readCounters(keys) batch-reads them. getDirectClient() is the raw ioredis escape hatch (null under Dapr).

Related sections