{"_id":"@boostpack/nestjs-redis","name":"@boostpack/nestjs-redis","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@boostpack/nestjs-redis","version":"1.0.0","description":"NestJS Redis module with first-class standalone, sentinel and cluster support","license":"MIT","contributors":[{"name":"Timur Popov","email":"timur2915@gmail.com"}],"repository":{"type":"git","url":"git+https://github.com/boostpack/boostpack.git","directory":"packages/nestjs/redis"},"main":"./dist/index.cjs.js","module":"./dist/index.esm.js","types":"./dist/index.d.ts","exports":{"./package.json":"./package.json",".":{"@boostpack/source":"./src/index.ts","types":"./dist/index.d.ts","import":"./dist/index.esm.js","require":"./dist/index.cjs.js","default":"./dist/index.esm.js"}},"keywords":["boostpack","redis","node-redis","nestjs","nest","cluster","sentinel","standalone","health","terminus","typescript","type-safe","type-safety"],"nx":{"tags":["scope:nestjs"]},"peerDependencies":{"@nestjs/common":">=10.0.0","@nestjs/terminus":">=11.0.0","redis":">=5.0.0","joi":">=17.0.0"},"peerDependenciesMeta":{"@nestjs/terminus":{"optional":true}},"dependencies":{"@boostpack/nestjs-config":"^1.0.2"},"gitHead":"3edfe9768d82c8fec48c9036aaf8df8c284b6ffc","_id":"@boostpack/nestjs-redis@1.0.0","bugs":{"url":"https://github.com/boostpack/boostpack/issues"},"homepage":"https://github.com/boostpack/boostpack#readme","_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-aFC0/J36Rprab+1iO3IloWh19YXV8aYpzGbeAtslWgj5Fs4L4OneiF5Mp89+E+o5p+PyaeZHZzXdx451bIFAbg==","shasum":"64b05b36cceaf3bc31d803a557fc2a99f7bd8447","tarball":"https://registry.npmjs.org/@boostpack/nestjs-redis/-/nestjs-redis-1.0.0.tgz","fileCount":41,"unpackedSize":69367,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDLXNl4Zy9XkP8derphWPzJ4gLanGjX5wVnhAybdmatTQIgXxEVpAq+XbU4D2PETdacBS8ogyiQ9sTqiL4vxANP3kA="}]},"_npmUser":{"name":"timur2915","email":"timur2915@gmail.com"},"directories":{},"maintainers":[{"name":"timur2915","email":"timur2915@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nestjs-redis_1.0.0_1780857628049_0.5432088387076313"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-07T18:40:27.863Z","1.0.0":"2026-06-07T18:40:28.194Z","modified":"2026-06-07T18:40:28.451Z"},"maintainers":[{"name":"timur2915","email":"timur2915@gmail.com"}],"description":"NestJS Redis module with first-class standalone, sentinel and cluster support","homepage":"https://github.com/boostpack/boostpack#readme","keywords":["boostpack","redis","node-redis","nestjs","nest","cluster","sentinel","standalone","health","terminus","typescript","type-safe","type-safety"],"repository":{"type":"git","url":"git+https://github.com/boostpack/boostpack.git","directory":"packages/nestjs/redis"},"contributors":[{"name":"Timur Popov","email":"timur2915@gmail.com"}],"bugs":{"url":"https://github.com/boostpack/boostpack/issues"},"license":"MIT","readme":"# @boostpack/nestjs-redis\n\nA production-ready, **type-safe** NestJS Redis module built on [node-redis](https://github.com/redis/node-redis). One module, three deployment modes — switch between **standalone**, **sentinel** and **cluster** without touching your code.\n\n## Why\n\n- **Type-safe end to end** — the config is a discriminated union (the compiler knows `sentinelGroupIdentifier` only exists in sentinel mode), and the injected client exposes typed, node-redis commands.\n- **Switch Redis modes for free** — `@InjectRedis()` resolves to `RedisClient`, the **intersection** of commands available in standalone, sentinel _and_ cluster. Write your code once against it and move between a single dev instance, a sentinel set, and a production cluster with an env var — no code changes, still fully typed.\n- **Effortless config** — `RedisModule.forRoot()` and you're done: it reads the standard `REDIS_*` env vars (validated) and gives you a connected client. Need explicit values or async config? Pass them in.\n- **Built-in health checks** — a `RedisHealthIndicator` ready for `@nestjs/terminus`, with **zero runtime dependency** on terminus (it's an optional peer). Recommended for production readiness probes.\n- **Resilient by default** — automatic reconnection, a crash-safe `error` handler on every connection (a Redis blip never takes down the process), lifecycle logging via the Nest `Logger`, optional background (`lazyConnect`) startup, and a non-blocking health check.\n- **Production essentials** — multiple named connections, validated configuration, graceful shutdown, and singleton/transient clients out of the box.\n\n## Installation\n\n```bash\nnpm install @boostpack/nestjs-redis redis @nestjs/common joi\n```\n\n`redis`, `@nestjs/common` and `joi` are peer dependencies. `@nestjs/terminus` is an **optional** peer — install it only for the health indicator.\n\n## Quick start\n\n```typescript\nimport { Module } from '@nestjs/common';\nimport { RedisModule } from '@boostpack/nestjs-redis';\n\n@Module({\n  imports: [RedisModule.forRoot()], // reads REDIS_* from the environment\n})\nexport class AppModule {}\n```\n\n```typescript\nimport { Injectable } from '@nestjs/common';\nimport { InjectRedis, RedisClient } from '@boostpack/nestjs-redis';\n\n@Injectable()\nexport class UserCache {\n  constructor(@InjectRedis() private readonly redis: RedisClient) {}\n\n  async cacheUser(id: string, data: unknown): Promise<void> {\n    await this.redis.set(`user:${id}`, JSON.stringify(data), { EX: 3600 });\n  }\n\n  async getUser(id: string): Promise<unknown> {\n    const cached = await this.redis.get(`user:${id}`);\n    return cached ? JSON.parse(cached) : null;\n  }\n}\n```\n\n> Commands use node-redis naming (camelCase): `hSet`, `hGetAll`, `zAdd`, `setEx`, …\n\nA fresh per-consumer connection is available via `@InjectTransientRedis()`.\n\n## Switching modes without changing code\n\n`@InjectRedis()` gives you `RedisClient` — the set of commands guaranteed to exist across **all** deployment modes. The same service works whether Redis is a single instance, sentinel-managed, or a cluster:\n\n```typescript\n// works against standalone, sentinel and cluster — picked at runtime by REDIS_MODE\n@Injectable()\nexport class RateLimiter {\n  constructor(@InjectRedis() private readonly redis: RedisClient) {}\n\n  async hit(key: string, ttlSeconds: number): Promise<number> {\n    const count = await this.redis.incr(key);\n    await this.redis.expire(key, ttlSeconds);\n    return count;\n  }\n}\n```\n\n```bash\n# dev\nREDIS_MODE=standalone REDIS_HOSTS=localhost:6379\n\n# production — same code, just env\nREDIS_MODE=cluster   REDIS_HOSTS=node1:6379,node2:6379,node3:6379\n```\n\nWhen you genuinely need mode-specific commands, inject a mode-specific type instead:\n\n```typescript\nimport { RedisClusterClient } from '@boostpack/nestjs-redis';\n\nconstructor(@InjectRedis() private readonly redis: RedisClusterClient) {}\n```\n\n- `RedisStandaloneClient` / `RedisSentinelClient` — node-redis `RedisClientType` / `RedisSentinelType`.\n- `RedisClusterClient` — node-redis `RedisClusterType`.\n\n## Configuration\n\n`forRoot` takes module-level options (`name`, `isGlobal`) and an optional, type-safe `config`. Omit `config` to read it from the environment.\n\n```typescript\nimport { RedisMode } from '@boostpack/nestjs-redis';\n\n// explicit, type-safe config\nRedisModule.forRoot({\n  config: { mode: RedisMode.Cluster, hosts: [{ host: 'node1', port: 6379 }] },\n});\n\n// async — build the config from a factory with injected deps\nRedisModule.forRootAsync({\n  inject: [ConfigService],\n  useFactory: (cfg: ConfigService) => ({\n    mode: RedisMode.Standalone,\n    hosts: [{ host: cfg.get('HOST'), port: 6379 }],\n  }),\n});\n```\n\nThe config is discriminated by `mode`, so each mode only accepts (and requires) the fields that apply to it:\n\n```typescript\ntype RedisStandaloneConfig = { mode: RedisMode.Standalone; hosts: RedisHost[]; password?: string; database?: number };\ntype RedisSentinelConfig = {\n  mode: RedisMode.Sentinel;\n  hosts: RedisHost[];\n  sentinelGroupIdentifier: string;\n  password?: string;\n  database?: number;\n};\ntype RedisClusterConfig = { mode: RedisMode.Cluster; hosts: RedisHost[]; password?: string };\n\n// each variant also accepts an `options?` passthrough to the matching node-redis factory\n// (createClient / createSentinel / createCluster) for advanced settings like\n// socket.tls, reconnectStrategy, connectTimeout, username, RESP, etc.\n```\n\n### Environment variables\n\n`forRoot()` (and `redisConfigFromEnv()`) read these standard names, validated with [Joi](https://joi.dev) via [`@boostpack/nestjs-config`](../config):\n\n| Variable                          | Required               | Description                                               |\n| --------------------------------- | ---------------------- | --------------------------------------------------------- |\n| `REDIS_MODE`                      | no (`standalone`)      | `standalone`, `sentinel` or `cluster`.                    |\n| `REDIS_HOSTS`                     | yes                    | Comma-separated `host:port` list (e.g. `localhost:6379`). |\n| `REDIS_PASSWORD`                  | no                     | Auth password.                                            |\n| `REDIS_DB`                        | no (forbidden cluster) | Database index (standalone / sentinel only).             |\n| `REDIS_SENTINEL_GROUP_IDENTIFIER` | sentinel only          | Sentinel master group name.                               |\n\n## Multiple connections\n\nGive each connection a `name`. A named connection reads its env vars from `REDIS_<NAME>_*` automatically — no extra wiring:\n\n```typescript\n@Module({\n  imports: [\n    RedisModule.forRoot({ name: 'cache' }), //    reads REDIS_CACHE_*\n    RedisModule.forRoot({ name: 'sessions' }), // reads REDIS_SESSIONS_*\n  ],\n})\nexport class AppModule {}\n```\n\n```typescript\n@Injectable()\nexport class SomeService {\n  constructor(\n    @InjectRedis('cache') private readonly cache: RedisClient,\n    @InjectRedis('sessions') private readonly sessions: RedisClient,\n  ) {}\n}\n```\n\nNamed connections accept an explicit `config` too:\n\n```typescript\nRedisModule.forRoot({\n  name: 'cache',\n  config: { mode: RedisMode.Standalone, hosts: [{ host: 'cache-host', port: 6379 }] },\n});\n```\n\n## Health checks\n\n**Recommended for production** — wire it into your **readiness** probe so orchestrators (e.g. Kubernetes) stop routing traffic to instances whose Redis connection is down, and resume once it recovers. Keep it out of the **liveness** probe: Redis is an external dependency, and a failed liveness check restarts the pod — which won't fix Redis and risks cascading restarts.\n\n`RedisHealthIndicator` is registered automatically and has **no runtime dependency** on `@nestjs/terminus` — it returns a terminus-compatible result. The check is **non-blocking**: if the connection isn't ready it reports `down` immediately instead of queuing a ping that would hang, and the ping is bounded by a timeout (default 1000ms, override with `pingCheck(key, { timeout })`). Inject it with `@InjectRedisHealth(name?)` and plug it into terminus when you want HTTP health checks:\n\n```typescript\nimport { Controller, Get } from '@nestjs/common';\nimport { HealthCheck, HealthCheckService } from '@nestjs/terminus';\nimport { InjectRedisHealth, RedisHealthIndicator } from '@boostpack/nestjs-redis';\n\n@Controller('health')\nexport class HealthController {\n  constructor(\n    private readonly health: HealthCheckService,\n    @InjectRedisHealth() private readonly redis: RedisHealthIndicator,\n  ) {}\n\n  @Get()\n  @HealthCheck()\n  check() {\n    return this.health.check([() => this.redis.pingCheck('redis')]);\n  }\n}\n```\n\n## Resilience\n\nEvery connection is created with an `error` handler attached, so a connection drop or reconnect failure is logged (via the Nest `Logger`, scoped `RedisModule:<name>`) instead of crashing the process with an unhandled `error` event. Lifecycle events (`ready`, `reconnecting`, `closed`) are logged too, and you can observe them yourself:\n\n```typescript\nRedisModule.forRoot({\n  onError: (error) => metrics.increment('redis.error', { message: error.message }),\n  onReady: () => log.info('redis ready'),\n});\n```\n\nAutomatic reconnection is provided by node-redis and enabled by default. On an unexpected socket drop the client reconnects with an exponential backoff (`min(2^retries × 50ms, 2000ms)` plus jitter); commands issued while disconnected are queued and flushed once the connection is restored. Cluster clients additionally rediscover topology on failover. Tune it via `config.options.socket.reconnectStrategy`.\n\n### Startup: `lazyConnect`\n\nBy default the connection is awaited during bootstrap. Because node-redis retries indefinitely, **an unreachable Redis blocks startup** until it comes up (or until a bounded `reconnectStrategy` gives up). For resilient startup, set `lazyConnect: true`: the app boots immediately, the connection is established in the background, commands issued meanwhile are queued, and the readiness probe gates traffic until it's ready.\n\n```typescript\nRedisModule.forRoot({ lazyConnect: true });\n```\n\n## Lifecycle\n\nThe module closes the singleton connection on application shutdown by default, in the last shutdown phase (`onApplicationShutdown`, after other providers' teardown) with a graceful `close()`. Enable Nest's shutdown hooks so it also fires on process signals:\n\n```typescript\napp.enableShutdownHooks();\n```\n\nIf your app manages the connection lifecycle itself (e.g. it shares the connection or drains it manually), opt out:\n\n```typescript\nRedisModule.forRoot({ closeOnShutdown: false });\n```\n\nTransient clients (`@InjectTransientRedis()`) are always owned by the consumer — close them yourself when done.\n\n","readmeFilename":"README.md","_rev":"1-57d80c4558c696dfb9d2d2d578cbc1af"}