npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ynode/redis

v1.7.3

Published

Fastify 5 plugin for Redis (node-redis) with automatic connection lifecycle, health checks, and namespace support.

Readme

@ynode/redis

Copyright (c) 2026 Michael Welter [email protected]

npm version License: MIT

A better Redis Fastify plugin that uses the official Redis library

Why?

A lightweight Fastify plugin that exposes a primary node‑redis client (redis package) on your Fastify instance and handles connection lifecycle (connect → ready → reconnect → close) for you.

  • ✅ Uses the official redis client (not ioredis)
  • ✅ Clean Fastify integration with proper startup/shutdown hooks
  • ✅ Simple API: fastify.redis everywhere in your app

If you are looking for the ioredis‑based plugin, see @fastify/redis.

Node.js support

This package requires Node.js 20.19.0 or newer. CI exercises the exact 20.19.0, 22.13.0, and 24.0.0 boundaries. Node.js 20 remains tested only to preserve the current major-version contract even though upstream support has ended; use Node.js 22 or 24 for supported production deployments. A newly released Node.js major is not considered supported until it is added to CI, even when the open engines range admits it.

Installation

Requires Node.js 20 or newer, Fastify 5, node-redis 6, and Redis server 7.2 or newer. Install the package and its Redis peer dependency:

npm install @ynode/redis redis

Usage

Register the plugin with your Fastify instance. The plugin consumes namespace, namespaceCommands, and startupTimeout; all other options are passed to the underlying node-redis createClient method.

import Fastify from "fastify";
import fastifyRedis from "@ynode/redis";

const fastify = Fastify({
    logger: true,
});

// Register the plugin with options
await fastify.register(fastifyRedis, {
    url: "redis://localhost:6379",
});

// Access the redis client from the fastify instance
fastify.get("/", async (request, reply) => {
    const value = await fastify.redis.get("mykey");
    return { key: "mykey", value: value };
});

const start = async () => {
    try {
        await fastify.listen({ port: 3000 });
    } catch (err) {
        fastify.log.error(err);
        process.exit(1);
    }
};

start();

Connection Lifecycle

This plugin manages Redis connection lifecycle using Fastify hooks:

  • Connects during Fastify startup (onReady)
  • Closes the Redis client during Fastify shutdown (onClose)

Startup is fail-fast. If Redis cannot be reached (or startup metadata commands fail), fastify.listen() rejects and the server will not start.

Startup is bounded to 10 seconds by default. Set startupTimeout: 0 to use node-redis connection behavior without a plugin-level deadline.

Managed duplicate and subscriber connections

Some Redis operations need an independent socket, especially pub/sub. Use the asynchronous managed factories instead of calling node-redis duplicate() directly:

const tenant = fastify.redis.withNamespace("codex");
const workerRedis = await tenant.createManagedClient({ name: "worker" });
const subscriber = await fastify.redis.createManagedSubscriber({ name: "events" });

await workerRedis.set("status", "ready"); // writes codex:status
await subscriber.subscribe("codex:events", (message) => {
    fastify.log.info({ message });
});
  • createManagedClient(options?) connects a duplicate with namespace, raw, scan, readiness, and healthcheck helpers. It inherits the factory client's active namespace unless options.namespace is provided.
  • createManagedSubscriber(options?) connects an intentionally un-namespaced duplicate with readiness and healthcheck helpers. Pub/sub channels remain global, so include any tenant identity in the channel protocol.

Both factories inherit the plugin's startupTimeout; override it per connection with options.startupTimeout. Managed startup is cancelled during Fastify shutdown, and every successfully created or still-connecting managed client is closed before the primary connection. Other options are forwarded to node-redis duplicate() without mutating the caller's object.

Key Namespacing

Use withNamespace(namespace) as the default, concurrency-safe way to scope keys. It returns a scoped client view without mutating global fastify.redis.namespace.

await fastify.register(fastifyRedis, {
    url: "redis://localhost:6379",
});

const tenantRedis = fastify.redis.withNamespace("codex");

await tenantRedis.set("status", "online"); // writes "codex:status"
await tenantRedis.get("status"); // reads "codex:status"

Scoped clients can safely coexist:

const tenantA = fastify.redis.withNamespace("alpha");
const tenantB = fastify.redis.withNamespace("beta");

await tenantA.set("counter", "1"); // alpha:counter
await tenantB.set("counter", "1"); // beta:counter

The mutable fastify.redis.namespace property is deprecated shared state and is retained only for backward compatibility. Its first assignment emits YNODE_REDIS_NAMESPACE_SETTER_DEPRECATED; use withNamespace() for concurrent request handling:

fastify.redis.namespace = "klingon";
await fastify.redis.set("status", "battle-ready"); // writes "klingon:status"

Namespace interception is tested against the supported node-redis v6 peer major, including its private MULTI/pipeline queue shape. If a future v6 release changes those internals in a way that prevents safe interception, the plugin fails closed with REDIS_NAMESPACE_INCOMPATIBLE_CLIENT instead of silently writing unprefixed keys.

To bypass namespacing for specific operations, use raw (works for base and scoped clients):

await fastify.redis.raw.get("status"); // reads the literal key "status" (no prefix)
await fastify.redis.raw.set("status", "manual"); // writes key "status" (no prefix)

const tenantRedis = fastify.redis.withNamespace("codex");
await tenantRedis.raw.get("status"); // still unprefixed

Advanced flows inherit the scope of the client that creates them:

const tenantRedis = fastify.redis.withNamespace("codex");

const transaction = tenantRedis.multi();
transaction.set("status", "ready").get("status");
await transaction.exec(); // operates on codex:status

const pipeline = tenantRedis.multi();
pipeline.set("counter", "1").get("counter");
await pipeline.execAsPipeline(); // operates on codex:counter

const rawTransaction = tenantRedis.raw.multi();
rawTransaction.set("status", "literal");
await rawTransaction.exec(); // operates on literal "status"

Hash, set, and sorted-set scan iterators retain the scope of the client that creates them. Logical keys are always prefixed, even when they already begin with the namespace text; use raw when you intentionally have a physical Redis key.

Namespacing is a key-rewriting convenience, not an authorization boundary. Database-wide commands such as SCAN, KEYS, and RANDOMKEY operate on the physical database and are not tenant-filtered. The raw/global scanIterator() behavior is intentionally unchanged. For an application-level inventory of one active namespace, use scanNamespaceIterator(): it prepends the active prefix to the logical MATCH glob, scans only that prefix, and yields logical keys with exactly one prefix removed. It requires an active namespace and preserves string or Buffer key replies.

const tenant = fastify.redis.withNamespace("klingon");

for await (const keys of tenant.scanNamespaceIterator({ MATCH: "session:*", COUNT: 100 })) {
    await tenant.mGet(keys);
}

A client returned directly by node-redis duplicate() is independent and is not namespaced or closed by this plugin. Use the lifecycle-managed client helpers below when the duplicate belongs to the Fastify application. Redis ACLs or separate databases/instances remain necessary when hostile tenants must be isolated.

Pub/sub channels are not Redis keys and cannot be prefixed transparently without changing application protocols. PUBLISH, SUBSCRIBE, PUBSUB, and their sharded and pattern variants therefore require client.raw whenever a namespace is active. Channel names remain global: include the tenant in the channel name yourself when tenant-scoped messaging is required.

Server/control-plane commands (ACL, CLIENT, CLUSTER, CONFIG, FUNCTION, MODULE, SCRIPT, persistence and replication controls), destructive database commands (FLUSHDB, FLUSHALL, SWAPDB), and connection-state commands (AUTH, HELLO, MONITOR, RESET, SELECT, direct transaction controls) fail closed with REDIS_NAMESPACE_UNSAFE_COMMAND whenever a namespace is active. Use client.raw only when the server-wide or connection-wide effect is intentional. The safe client.multi()/pipeline factories remain namespace-aware; a raw transaction must be created with client.raw.multi().

Namespace safety migration

Namespaced clients in earlier releases allowed pub/sub, monitor/reset/select, and generated administrative methods to reach Redis without a namespace-local meaning. These operations now require an explicit raw client. Replace calls such as tenant.subscribe(...), tenant.select(...), or tenant.configGet(...) with tenant.raw.subscribe(...), tenant.raw.select(...), or tenant.raw.configGet(...), and include the tenant identifier in pub/sub channel names where isolation is expected. Key-bearing transaction and pipeline commands continue to use tenant.multi() and are prefixed automatically.

Commands whose key positions Redis reports as movable are rewritten when the plugin has an exact resolver. If an active namespace cannot be applied safely, the command fails closed instead of running against unprefixed keys; use raw only when that database-wide access is intentional.

For Redis modules or custom commands that are not available through COMMAND introspection, provide explicit key metadata with namespaceCommands:

await fastify.register(fastifyRedis, {
    namespace: "codex",
    namespaceCommands: {
        MODULEKEY: { firstKey: 1, lastKey: 1 },
        MODULEMSET: { firstKey: 1, lastKey: -1, step: 2 },
        "MODULE.INFO": { keyless: true },
    },
});

fastify.redis.registerNamespaceCommand("MODULEGET", { firstKey: 1, lastKey: 1 });

Command indexes are one-based and refer to the arguments after the command name. Use { keyless: true } only for commands whose arguments are never Redis keys.

Namespacing rewrites command inputs, not Redis replies. Commands that return key names, such as the pop families, can therefore return their physical prefixed names.

Health and Readiness

The primary client and every managed connection expose simple probe helpers:

  • fastify.redis.readiness(): lightweight state snapshot
  • fastify.redis.healthcheck(): ping-based health check that never throws

The healthcheck ping is bounded by a deadline so a stalled connection resolves unhealthy instead of hanging the probe. The default deadline is 5000 ms; override it per call with healthcheck({ timeoutMs }).

const readiness = fastify.redis.readiness();
// { isOpen: true, isReady: true, namespace: "codex" }

const health = await fastify.redis.healthcheck();
// { ok: true, ping: "PONG", latencyMs: 1, isOpen: true, isReady: true, namespace: "codex" }

Options

Plugin-specific options

  • name (string, optional): connection name used with Redis CLIENT SETNAME. Default: @ynode/redis
  • namespace (string, optional): key prefix for Redis commands that operate on keys. : is reserved as the separator; a trailing colon is normalized away, while embedded colons are rejected. Control characters, embedded whitespace, and the glob metacharacters *, ?, [, and ] are also rejected. Example: namespace: "codex" prefixes keys as codex:<key>.
  • namespaceCommands (object or Map, optional): custom command key metadata for Redis modules or deployments where COMMAND introspection is unavailable. Each command can define { firstKey, lastKey, step } key positions or { keyless: true }.
  • startupTimeout (number, default: 10000): maximum startup time in milliseconds. Set to 0 to disable the plugin deadline.

Managed client factories also consume startupTimeout. createManagedClient() additionally consumes namespace and namespaceCommands; when omitted, it inherits the active namespace and registration-time command metadata. These managed-only fields are never forwarded to node-redis duplicate(). createManagedSubscriber() rejects namespace options because Redis channels are not keys.

Redis client options

All other options are passed directly to the createClient function from the official redis library.

For a full list of available options, please see the official node-redis documentation.

TypeScript

This package ships TypeScript declarations, including Fastify instance augmentation for fastify.redis.

import Fastify from "fastify";
import fastifyRedis from "@ynode/redis";

const app = Fastify();
await app.register(fastifyRedis, { url: "redis://localhost:6379" });

await app.redis.set("health", "ok");

Testing and CI

  • npm test runs unit and integration tests. Use npm run lint and npm run format:check for static checks.
  • Integration tests use REDIS_URL when provided.
  • If REDIS_URL is not set, tests try to start a local redis-server automatically.
  • CI runs on push and pull request, starts a Redis service, and executes formatting, lint, and test gates.

License

This project is licensed under the MIT License.