@nathapp/nestjs-redis
v3.4.1
Published
NestJS ioredis module with multi-client support and Redlock-based distributed locking
Readme
NOTES
This package originated as a fork of skunight/nestjs-redis (MIT licensed) and was upgraded to support NestJS 8/9/10/11.
This module has added RedisLockService by using 'redlock' to make use of redis distributed lock
module registration had a backwards compatibility of RedisModule.register(options) (RedisModuleOptions) or RedisModule.registerAsync(options) (RedisModuleAsyncOptions) which register default setting of RedisLockService, if you would like to change the lockSetting, please use RedisRegisterOptions or RedisRegisterAsyncOptions on module registration
Nestjs Redis
Redis component for NestJs.
Installation
Yarn
yarn add @nathapp/nestjs-redisNPM
npm install @nathapp/nestjs-redis --saveGetting Started
Let's register the RedisModule in app.module.ts
import { Module } from '@nestjs/common'
import { RedisModule} from '@nathapp/nestjs-redis'
@Module({
imports: [
RedisModule.register(options)
],
})
export class AppModule {}With Async
import { Module } from '@nathapp/nestjs-redis';
import { RedisModule} from 'nestjs-redis'
@Module({
imports: [
RedisModule.forRootAsync({
useFactory: (configService: ConfigService) => configService.get('redis'), // or use async method
//useFactory: async (configService: ConfigService) => configService.get('redis'),
inject:[ConfigService]
}),
],
})
export class AppModule {}And the config file look like this With single client
export default {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT),
db: parseInt(process.env.REDIS_DB),
password: process.env.REDIS_PASSWORD,
keyPrefix: process.env.REDIS_PRIFIX,
}
Or
export default {
url: 'redis://:[email protected]:6380/4',
}With custom error handler
export default {
url: 'redis://:[email protected]:6380/4',
onClientReady: (client) => {
client.on('error', (err) => {}
)},
}With multi client
export default [
{
name:'test1',
url: 'redis://:[email protected]:6380/4',
},
{
name:'test2',
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT),
db: parseInt(process.env.REDIS_DB),
password: process.env.REDIS_PASSWORD,
keyPrefix: process.env.REDIS_PRIFIX,
},
]And use in your service
import { Injectable } from '@nestjs/common';
import { RedisService } from 'nestjs-redis';
@Injectable()
export class TestService {
constructor(
private readonly redisService: RedisService,
) { }
async root(): Promise<boolean> {
const client = await this.redisService.getClient('test')
return true
}
}Options
interface RedisOptions {
/**
* client name. default is a uuid, unique.
*/
name?: string;
url?: string;
port?: number;
host?: string;
/**
* 4 (IPv4) or 6 (IPv6), Defaults to 4.
*/
family?: number;
/**
* Local domain socket path. If set the port, host and family will be ignored.
*/
path?: string;
/**
* TCP KeepAlive on the socket with a X ms delay before start. Set to a non-number value to disable keepAlive.
*/
keepAlive?: number;
connectionName?: string;
/**
* If set, client will send AUTH command with the value of this option when connected.
*/
password?: string;
/**
* Database index to use.
*/
db?: number;
/**
* When a connection is established to the Redis server, the server might still be loading
* the database from disk. While loading, the server not respond to any commands.
* To work around this, when this option is true, ioredis will check the status of the Redis server,
* and when the Redis server is able to process commands, a ready event will be emitted.
*/
enableReadyCheck?: boolean;
keyPrefix?: string;
/**
* When the return value isn't a number, ioredis will stop trying to reconnect.
* Fixed in: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/15858
*/
retryStrategy?(times: number): number | false;
/**
* By default, all pending commands will be flushed with an error every
* 20 retry attempts. That makes sure commands won't wait forever when
* the connection is down. You can change this behavior by setting
* `maxRetriesPerRequest`.
*
* Set maxRetriesPerRequest to `null` to disable this behavior, and
* every command will wait forever until the connection is alive again
* (which is the default behavior before ioredis v4).
*/
maxRetriesPerRequest?: number | null;
/**
* 1/true means reconnect, 2 means reconnect and resend failed command. Returning false will ignore
* the error and do nothing.
*/
reconnectOnError?(error: Error): boolean | 1 | 2;
/**
* By default, if there is no active connection to the Redis server, commands are added to a queue
* and are executed once the connection is "ready" (when enableReadyCheck is true, "ready" means
* the Redis server has loaded the database from disk, otherwise means the connection to the Redis
* server has been established). If this option is false, when execute the command when the connection
* isn't ready, an error will be returned.
*/
enableOfflineQueue?: boolean;
/**
* The milliseconds before a timeout occurs during the initial connection to the Redis server.
* default: 10000.
*/
connectTimeout?: number;
/**
* After reconnected, if the previous connection was in the subscriber mode, client will auto re-subscribe these channels.
* default: true.
*/
autoResubscribe?: boolean;
/**
* If true, client will resend unfulfilled commands(e.g. block commands) in the previous connection when reconnected.
* default: true.
*/
autoResendUnfulfilledCommands?: boolean;
lazyConnect?: boolean;
tls?: tls.ConnectionOptions;
sentinels?: Array<{ host: string; port: number; }>;
name?: string;
/**
* Enable READONLY mode for the connection. Only available for cluster mode.
* default: false.
*/
readOnly?: boolean;
/**
* If you are using the hiredis parser, it's highly recommended to enable this option.
* Create another instance with dropBufferSupport disabled for other commands that you want to return binary instead of string
*/
dropBufferSupport?: boolean;
/**
* Whether to show a friendly error stack. Will decrease the performance significantly.
*/
showFriendlyErrorStack?: boolean;
}Note: when url is set it is passed to ioredis alongside the remaining options, not
instead of them. Connection fields encoded in the URL win; everything else you supply
(lazyConnect, tls, retryStrategy, ...) still applies.
Distributed locking
RedisLockService wraps redlock. Use
withLock to run a routine under a lock that is always released:
await this.lockService.withLock('onboarding:user-42', async () => {
await this.advanceOnboarding();
});acquireLock(name, expiryMs?) / releaseLock(token) are available when the critical
section does not fit a callback. acquireLock returns a token identifying that specific
handle — pass the token, not the lock name, to releaseLock.
withLock and acquireLock both take an optional trailing settings argument that
overrides the module-wide settings for that call only (releaseLock does not):
// this one call waits its turn; every other lock in the app still fails fast
await this.lockService.withLock('slow-job', run, 60000, { retryCount: 10 });Module-wide settings are supplied once via lockSettings and are merged over the
defaults, so overriding one field leaves the others intact:
RedisModule.register({
redisOptions: { url: 'redis://localhost:6379' },
lockSettings: { settings: { retryCount: 3 } },
})Note the nesting: once you pass lockSettings, the connection options must go under
redisOptions. Leaving them at the top level alongside lockSettings throws at
registration rather than silently connecting to the ioredis default host.
| Setting | Default | Meaning |
| --- | --- | --- |
| retryCount | 0 | Attempts before a contended acquisition throws. 0 fails fast; -1 retries forever. |
| retryDelay | 200 | Base milliseconds between attempts. |
| retryJitter | 200 | Symmetric jitter: each delay is retryDelay ± retryJitter. |
| driftFactor | 0.01 | Multiplied by the lock TTL to determine clock drift allowance. |
| automaticExtensionThreshold | 500 | Only applies to redlock's using(), which this service does not call — currently inert. |
The default is fail-fast: retryCount: 0 means a contended acquisition throws
immediately rather than waiting. This suits the try-lock pattern ("if this fails,
someone else owns it, skip") and never blocks a caller behind a peer.
It also means this service is not a drop-in for an in-process mutex that queues.
If a call site needs to wait its turn, opt into retries explicitly — per module via
lockSettings, or per call as shown above. Note redlock's own default is retryCount:
10; this package deliberately differs.
Each retry waits a random [0, retryDelay + retryJitter)ms, so retryCount: 10 with
the defaults gives roughly two seconds of patience on average and up to about four.
Any wait is bounded — a lock is never queued indefinitely, and a holder whose process
dies blocks its key only until expiryMs (default 60s) elapses.
That's it!
