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

@fluojs/redis

v2.0.2

Published

App-scoped Redis lifecycle management, raw client injection, and RedisService facade for Fluo.

Readme

@fluojs/redis

Node.js support is >=24.0.0 <27. See Node.js support and migration before upgrading.

Shared Redis connection layer for fluo. It provides a default app-scoped ioredis client plus optional named clients, all managed by the application lifecycle.

Table of Contents

Installation

npm install @fluojs/redis ioredis

When to Use

  • When you need a shared Redis connection across multiple modules (caching, queues, throttlers).
  • When you want automatic connection management (connect on bootstrap, quit on shutdown).
  • When you need a JSON-aware facade for common key-value operations.

Quick Start

Register the Module

Use RedisModule.forRoot(options) to register the default Redis client and RedisService facade.

RedisModule.forRoot(...) is intentionally synchronous and always creates a new ioredis client from Redis constructor options; it does not adopt an externally constructed client. When migrating from NestJS async dynamic modules such as forRootAsync(...), resolve secrets, environment-specific hosts, and TLS options at the application boundary first, then pass the final Redis options into forRoot(...). Keep externally constructed clients outside this module and close them from the application lifecycle. fluo does not defer Redis module wiring to an async factory hidden inside the module graph.

import { Module } from '@fluojs/core';
import { RedisModule } from '@fluojs/redis';

@Module({
  imports: [
    RedisModule.forRoot({
      host: 'localhost',
      port: 6379,
    }),
  ],
})
export class AppModule {}

Use the Redis Service

Inject RedisService for high-level operations or REDIS_CLIENT for the raw ioredis instance.

RedisService.get() attempts JSON parsing and falls back to the raw string; missing keys return null. RedisService.set() serializes values with JSON.stringify(): finite positive integer TTLs use Redis EX, while finite positive fractional TTLs use PX with milliseconds rounded up. Omit the TTL or pass a non-positive or non-finite value for a persistent key.

import { Inject } from '@fluojs/core';
import { RedisService } from '@fluojs/redis';

@Inject(RedisService)
export class CacheRepository {
  constructor(private readonly redis: RedisService) {}

  async saveUser(id: string, user: object) {
    await this.redis.set(`user:${id}`, user, 3600);
  }

  async getUser(id: string) {
    return await this.redis.get(`user:${id}`);
  }
}

Common Patterns

Lifecycle Ownership

Every RedisModule.forRoot(...) registration creates a new client that @fluojs/redis owns, including named clients registered through RedisModule.forRoot({ name, ... }). The module never adopts an existing client instance. Registration identity is application-wide: only one unnamed default registration and one registration for each trimmed name are allowed. Compatible duplicate package copies share the exported default/named token identities and the registration marker, so mixed-copy duplicates are rejected during bootstrap before creating a Redis client. Ownership remains isolated per application; private lifecycle/guard tokens and the default RedisService constructor token keep their existing policy.

  • Fluo always forces lazyConnect: true, even if callers cast options manually, so sockets open during application bootstrap instead of import time.
  • During bootstrap, the lifecycle service only calls connect() while the client is still in ioredis wait state.
  • Lifecycle-owned connect() and quit() calls are bounded by package timeouts (10_000 ms by default) so bootstrap and shutdown do not wait forever on a stalled Redis command. Override them with lifecycle.connectTimeoutMs and lifecycle.quitTimeoutMs; pass 0 only when the host process intentionally owns an unbounded wait.
  • During shutdown, ready/connecting clients attempt quit() first for graceful teardown, while monitoring, wait, and closed-transition states use disconnect() directly.
  • If quit() fails, Fluo falls back to disconnect() and only rethrows when the client still remains open afterward.

Option Normalization

RedisModuleOptions is the caller-facing input to RedisModule.forRoot(...), not the final ioredis constructor shape. It accepts ordinary ioredis options except lazyConnect and name, then adds four Fluo-only fields:

  • name identifies the Fluo registration and its DI tokens. It never becomes the ioredis constructor name. Each trimmed name is an application-wide ownership identity and can be registered only once.
  • global controls module visibility. The default registration is global unless set to false; named registrations are always scoped and reject global: true.
  • lifecycle configures Fluo-owned connect() and quit() timeout guardrails.
  • sentinelName supplies the ioredis Sentinel master name because the input name field is reserved for Fluo registration identity.

Normalization happens before client construction in this order:

  1. Fluo removes name, global, lifecycle, and sentinelName from the ordinary ioredis options.
  2. It trims the registration name, then normalizes and validates the lifecycle timeouts.
  3. It rejects a blank registration name and rejects global: true on a named registration.
  4. It maps sentinelName, when present, to the ioredis constructor name and produces RedisClientOptions.
  5. The provider creates the client with { ...clientOptions, lazyConnect: true }. This final assignment always wins, including when a caller bypasses the type restriction and casts lazyConnect: false into the input.

RedisClientOptions therefore describes the normalized constructor-facing options consumed by the provider factory: ordinary ioredis options plus an optional Sentinel name, but no Fluo-only fields and no caller-controlled lazyConnect. It is not a synonym for the RedisModuleOptions accepted by forRoot(...).

Named Clients

Use RedisModule.forRoot({ name, ...options }) when one application needs more than one Redis connection. RedisModule.forRoot(options) without name provides the default REDIS_CLIENT and RedisService aliases, and named registrations are resolved with getRedisClientToken(name) and getRedisServiceToken(name).

  • Omit name when you want the default aliases: REDIS_CLIENT / RedisService.
  • Pass name when you want the named helpers: getRedisClientToken(name) / getRedisServiceToken(name).
  • name identifies the Fluo registration only. For an ioredis Sentinel master name, pass sentinelName; Fluo forwards it to ioredis as its constructor name option without changing the registration tokens.
  • Named clients follow the same bootstrap/shutdown contract as the default client; only the default registration exports the REDIS_CLIENT / RedisService aliases.
  • Names are trimmed, and blank or whitespace-only names are rejected by token/component helpers.
import { Module, Inject } from '@fluojs/core';
import type Redis from 'ioredis';
import {
  getRedisClientToken,
  getRedisServiceToken,
  RedisModule,
  RedisService,
} from '@fluojs/redis';

const ANALYTICS_REDIS = getRedisServiceToken('analytics');
const ANALYTICS_REDIS_CLIENT = getRedisClientToken('analytics');

@Module({
  imports: [
    RedisModule.forRoot({ host: 'localhost', port: 6379 }),
    RedisModule.forRoot({ name: 'analytics', host: 'localhost', port: 6380 }),
    RedisModule.forRoot({ name: 'sentinel-cache', sentinelName: 'mymaster', sentinels: [{ host: 'localhost', port: 26379 }] }),
  ],
})
export class AppModule {}

@Inject(RedisService, ANALYTICS_REDIS, ANALYTICS_REDIS_CLIENT)
export class AnalyticsStore {
  constructor(
    private readonly defaultRedis: RedisService,
    private readonly analyticsRedis: RedisService,
    private readonly analyticsClient: Redis,
  ) {}
}

Raw Client Access

If you need advanced Redis commands (pipelines or Lua scripts), inject the raw client directly.

If you already injected RedisService, call redis.getRawClient() to access the same underlying ioredis instance.

The raw client retains the full ioredis API, including duplicate, watch, and multi. @fluojs/cache-manager atomic updates consume that existing seam; RedisService does not gain an update method and Redis module options do not gain atomicUpdates. Opt in on the cache registration with redis: { atomicUpdates: true }. Cache-manager owns the exported optional RedisAtomicClient / RedisAtomicTransaction structural types and its isolated duplicate(...) connections, disconnecting each in finally. It never closes the shared injected raw client. This is separate from an application-created duplicate, whose lifecycle remains application-owned. Redis topology, namespace metadata, TTL, retry, and cancellation requirements belong to the linked cache API owner. Operation-owned connections disable reconnection: connection loss propagates the client error instead of committing on a replacement connection without WATCH. The shared client retains its reconnection policy.

Redis Pub/Sub is the exception to ordinary shared-client reuse: Redis switches subscribed connections into subscribe mode, so do not use the lifecycle-managed REDIS_CLIENT or a RedisService.getRawClient() result as both publisher and subscriber. Create a dedicated subscriber connection with client.duplicate() (or an explicitly named RedisModule.forRoot({ name: 'subscriber', ... }) registration) and let that connection have its own lifecycle owner.

If you use client.duplicate(), the duplicate is application-owned: connect it, subscribe with it, and close it from your own shutdown path. If you want fluo to own subscriber startup/shutdown timeouts, prefer a named registration and inject it with getRedisClientToken(name).

import { Inject } from '@fluojs/core';
import { REDIS_CLIENT } from '@fluojs/redis';
import type Redis from 'ioredis';

@Inject(REDIS_CLIENT)
export class AdvancedService {
  constructor(private readonly client: Redis) {}

  async executeComplex() {
    return await this.client.pipeline().set('foo', 'bar').get('foo').exec();
  }
}
import { Inject, Module } from '@fluojs/core';
import { getRedisClientToken, RedisModule } from '@fluojs/redis';
import { RedisPubSubMicroserviceTransport } from '@fluojs/microservices';
import type Redis from 'ioredis';

const COMMAND_REDIS = getRedisClientToken();
const SUBSCRIBER_REDIS = getRedisClientToken('subscriber');

@Inject(COMMAND_REDIS, SUBSCRIBER_REDIS)
export class PubSubTransportFactory {
  constructor(
    private readonly commandClient: Redis,
    private readonly subscriberClient: Redis,
  ) {}

  createTransport() {
    return new RedisPubSubMicroserviceTransport({
      publishClient: this.commandClient,
      subscribeClient: this.subscriberClient,
    });
  }
}

@Module({
  imports: [
    RedisModule.forRoot({ host: 'localhost', port: 6379 }),
    RedisModule.forRoot({ name: 'subscriber', host: 'localhost', port: 6379 }),
  ],
  providers: [PubSubTransportFactory],
})
export class RedisConnectionsModule {}

@Inject(...) only declares the constructor tokens of a class; it does not register that class. fluo resolves providers from explicit module registration, so PubSubTransportFactory must be listed in the providers array of the module that imports the Redis registrations it depends on. A decorated class that no module lists is not resolvable.

Public API Overview

Core

  • RedisModule: Registers Redis clients and lifecycle hooks.
  • RedisModule.forRoot(options): Registers the default Redis client plus RedisService facade when name is omitted, or an additional named Redis client when name is provided. The default registration is global by default; named registrations are scoped. Use lifecycle.connectTimeoutMs and lifecycle.quitTimeoutMs to bound Fluo-owned connection startup and shutdown.
  • Lifecycle hooks are configured only through RedisModule.forRoot(...).lifecycle; the internal lifecycle service is intentionally not part of the public API.
  • RedisService: Facade with JSON codec support and get/set/del methods.
  • REDIS_CLIENT: DI token for the underlying ioredis instance.
  • DEFAULT_REDIS_CLIENT_NAME: Stable default Redis client name.
  • getRedisClientToken(name): DI token helper for a named raw client. Omitting name returns the default REDIS_CLIENT token.
  • getRedisServiceToken(name): DI token helper for a named RedisService facade. Omitting name returns the default RedisService token.
  • getRedisComponentId(name): Status/dependency id helper used by Redis-consuming packages (redis.default, redis.cache, etc.).
  • createRedisPlatformStatusSnapshot(input): Adapts Redis connection state into Fluo's platform health/readiness snapshot contract.

Types

  • DefaultRedisModuleOptions: Options accepted by the unnamed default Redis registration, including optional global alias visibility and lifecycle timeout controls.
  • NamedRedisModuleOptions: Options accepted by additional named Redis registrations, including required name and scoped lifecycle timeout controls.
  • RedisModuleOptions: Caller-facing union accepted by RedisModule.forRoot(...). It combines ordinary ioredis options except lazyConnect and name with the Fluo-only name, global, lifecycle, and sentinelName fields.
  • RedisClientOptions: Normalized constructor-facing options after Fluo removes its module-only fields and maps sentinelName to the ioredis Sentinel name. The provider adds lazyConnect: true afterward as the final override.
  • RedisLifecycleOptions: Optional timeout controls for Fluo-owned connect() and quit() lifecycle commands.
  • PersistencePlatformStatusSnapshot, RedisStatusAdapterInput: Status snapshot input/output types.

Related Packages

  • @fluojs/cache-manager: Uses this package for Redis-backed caching.
  • @fluojs/queue: Uses this package for distributed job processing.
  • @fluojs/throttler: Uses this package for distributed rate limiting.

Example Sources

  • packages/redis/src/module.test.ts: Module lifecycle and DI wiring.
  • packages/redis/src/public-api.test.ts: Root-barrel export guard for the documented Redis surface.
  • packages/redis/src/redis-service.ts: Facade implementation and codec logic.

Compatible service copies

Default public Redis service classes support compatible-copy class injection. Named Redis clients and services remain name-isolated.