{"id":50672531,"url":"https://github.com/alexpota/nestjs-redlock-universal","last_synced_at":"2026-06-08T12:31:34.258Z","repository":{"id":322804997,"uuid":"1090963855","full_name":"alexpota/nestjs-redlock-universal","owner":"alexpota","description":"NestJS integration for redlock-universal - Distributed Redis and Valkey locks with decorators and dependency injection","archived":false,"fork":false,"pushed_at":"2026-04-04T12:37:58.000Z","size":445,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-04T14:42:15.677Z","etag":null,"topics":["concurrency","decorator","dependency-injection","distributed-locking","distributed-systems","ioredis","microservices","mutex","nest-framework","nestjs","node-redis","redis","redis-lock","redlock","typescript","valkey","valkey-glide"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/nestjs-redlock-universal","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexpota.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":"audit-ci.json","citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"alexpota"}},"created_at":"2025-11-06T11:25:39.000Z","updated_at":"2026-04-04T12:38:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"fff05409-c80d-4395-b656-356ad710270c","html_url":"https://github.com/alexpota/nestjs-redlock-universal","commit_stats":null,"previous_names":["alexpota/nestjs-redlock-universal"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/alexpota/nestjs-redlock-universal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexpota%2Fnestjs-redlock-universal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexpota%2Fnestjs-redlock-universal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexpota%2Fnestjs-redlock-universal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexpota%2Fnestjs-redlock-universal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexpota","download_url":"https://codeload.github.com/alexpota/nestjs-redlock-universal/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexpota%2Fnestjs-redlock-universal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34063149,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["concurrency","decorator","dependency-injection","distributed-locking","distributed-systems","ioredis","microservices","mutex","nest-framework","nestjs","node-redis","redis","redis-lock","redlock","typescript","valkey","valkey-glide"],"created_at":"2026-06-08T12:31:33.364Z","updated_at":"2026-06-08T12:31:34.252Z","avatar_url":"https://github.com/alexpota.png","language":"TypeScript","funding_links":["https://github.com/sponsors/alexpota"],"categories":[],"sub_categories":[],"readme":"# nestjs-redlock-universal\n\n\u003e NestJS integration for [redlock-universal](https://www.npmjs.com/package/redlock-universal) - Distributed Redis and Valkey locks with decorators and dependency injection\n\n[![npm version](https://img.shields.io/npm/v/nestjs-redlock-universal.svg)](https://www.npmjs.com/package/nestjs-redlock-universal)\n[![npm downloads](https://img.shields.io/npm/dm/nestjs-redlock-universal.svg)](https://www.npmjs.com/package/nestjs-redlock-universal)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Sponsor](https://img.shields.io/badge/Sponsor-%E2%9D%A4-pink?logo=github)](https://github.com/sponsors/alexpota)\n\n## Overview\n\nNestJS wrapper for [redlock-universal](https://www.npmjs.com/package/redlock-universal), providing **distributed Redis and Valkey locks** through NestJS decorators, modules, and dependency injection.\n\n## Features\n\n- **NestJS Native**: First-class integration with dependency injection and lifecycle hooks\n- **Distributed Locks**: Redlock algorithm for multi-instance Redis/Valkey setups\n- **Simple API**: Method decorator for zero-boilerplate distributed locking\n- **High Performance**: \u003c1ms lock acquisition with automatic extension\n- **Type-Safe**: Full TypeScript support with strict type checking\n- **Universal**: Works with `node-redis` v4+, `ioredis` v5+, and Valkey GLIDE v2+\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Configuration](#configuration)\n  - [Using ioredis](#using-ioredis)\n  - [Using Valkey GLIDE](#using-valkey-glide)\n- [API Reference](#api-reference)\n- [Advanced Usage](#advanced-usage)\n- [Testing](#testing)\n- [Troubleshooting](#troubleshooting)\n- [License](#license)\n\n## Installation\n\nThis package wraps `redlock-universal`, so you need to install both packages:\n\n```bash\nnpm install nestjs-redlock-universal redlock-universal\n```\n\nYou'll also need a Redis/Valkey client:\n\n```bash\n# For node-redis\nnpm install redis\n\n# OR for ioredis\nnpm install ioredis\n\n# OR for Valkey GLIDE (official Valkey client)\nnpm install @valkey/valkey-glide\n```\n\n## Quick Start\n\n### 1. Configure the Module\n\n```typescript\nimport { Module } from '@nestjs/common';\nimport { RedlockModule, NodeRedisAdapter } from 'nestjs-redlock-universal';\nimport { createClient } from 'redis';\n\n// Create and connect Redis clients\nconst redis1 = createClient({ url: 'redis://localhost:6379' });\nconst redis2 = createClient({ url: 'redis://localhost:6380' });\nconst redis3 = createClient({ url: 'redis://localhost:6381' });\n\nawait Promise.all([redis1.connect(), redis2.connect(), redis3.connect()]);\n\n@Module({\n  imports: [\n    RedlockModule.forRoot({\n      nodes: [\n        new NodeRedisAdapter(redis1),\n        new NodeRedisAdapter(redis2),\n        new NodeRedisAdapter(redis3),\n      ],\n      defaultTtl: 30000, // 30 seconds\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### 2. Use the `@Redlock` Decorator\n\n```typescript\nimport { Injectable } from '@nestjs/common';\nimport { Redlock } from 'nestjs-redlock-universal';\n\n@Injectable()\nexport class PaymentService {\n  @Redlock({ key: 'payment:processing' })\n  async processPayment(orderId: string): Promise\u003cvoid\u003e {\n    // This method is automatically protected by a distributed lock\n    // Only one instance can execute at a time across all servers\n  }\n\n  @Redlock({ key: (userId: string) =\u003e `user:${userId}:update` })\n  async updateUser(userId: string, data: unknown): Promise\u003cvoid\u003e {\n    // Lock key is dynamically generated from method arguments\n    // Each user gets their own lock\n  }\n}\n```\n\n### 3. Or Use the Service Directly\n\n```typescript\nimport { Injectable } from '@nestjs/common';\nimport { RedlockService } from 'nestjs-redlock-universal';\n\n@Injectable()\nexport class OrderService {\n  constructor(private readonly redlock: RedlockService) {}\n\n  async processOrder(orderId: string): Promise\u003cvoid\u003e {\n    // Recommended: Automatic lock management with using()\n    await this.redlock.using(`order:${orderId}`, async () =\u003e {\n      // Lock is automatically extended if operation takes longer than TTL\n      // Lock is automatically released when done or on error\n    });\n  }\n\n  async manualLocking(resourceId: string): Promise\u003cvoid\u003e {\n    // Advanced: Manual acquire/release for fine-grained control\n    const handle = await this.redlock.acquire(`resource:${resourceId}`);\n    try {\n      // Critical section\n    } finally {\n      await this.redlock.release(`resource:${resourceId}`, handle);\n    }\n  }\n}\n```\n\n## Configuration\n\n### Synchronous Configuration\n\n```typescript\nimport { RedlockModule, NodeRedisAdapter } from 'nestjs-redlock-universal';\n\nRedlockModule.forRoot({\n  nodes: [\n    new NodeRedisAdapter(redis1),\n    new NodeRedisAdapter(redis2),\n    new NodeRedisAdapter(redis3),\n  ],\n  defaultTtl: 30000,        // Default lock TTL in milliseconds\n  retryAttempts: 3,         // Number of retry attempts\n  retryDelay: 200,          // Delay between retries in milliseconds\n  quorum: 2,                // Minimum nodes for quorum (default: majority)\n  logger: winstonLogger,    // Optional: Winston, Pino, or Bunyan logger\n})\n```\n\n### Asynchronous Configuration\n\n```typescript\nimport { ConfigService } from '@nestjs/config';\nimport { RedlockModule, NodeRedisAdapter } from 'nestjs-redlock-universal';\n\nRedlockModule.forRootAsync({\n  useFactory: async (configService: ConfigService) =\u003e {\n    const redisUrls = configService.get\u003cstring[]\u003e('redis.urls');\n    const clients = await Promise.all(\n      redisUrls.map(url =\u003e createClient({ url }).connect())\n    );\n\n    return {\n      nodes: clients.map(client =\u003e new NodeRedisAdapter(client)),\n      defaultTtl: configService.get('redis.lockTtl', 30000),\n    };\n  },\n  inject: [ConfigService],\n})\n```\n\n### Using ioredis\n\n```typescript\nimport { RedlockModule, IoredisAdapter } from 'nestjs-redlock-universal';\nimport Redis from 'ioredis';\n\nconst redis1 = new Redis({ host: 'localhost', port: 6379 });\nconst redis2 = new Redis({ host: 'localhost', port: 6380 });\nconst redis3 = new Redis({ host: 'localhost', port: 6381 });\n\nRedlockModule.forRoot({\n  nodes: [\n    new IoredisAdapter(redis1),\n    new IoredisAdapter(redis2),\n    new IoredisAdapter(redis3),\n  ],\n})\n```\n\n### Using Valkey GLIDE\n\n```typescript\nimport { RedlockModule, GlideAdapter } from 'nestjs-redlock-universal';\nimport { GlideClient } from '@valkey/valkey-glide';\n\n// Create Valkey GLIDE clients\nconst valkey1 = await GlideClient.createClient({\n  addresses: [{ host: 'localhost', port: 6379 }],\n});\nconst valkey2 = await GlideClient.createClient({\n  addresses: [{ host: 'localhost', port: 6380 }],\n});\nconst valkey3 = await GlideClient.createClient({\n  addresses: [{ host: 'localhost', port: 6381 }],\n});\n\nRedlockModule.forRoot({\n  nodes: [\n    new GlideAdapter(valkey1),\n    new GlideAdapter(valkey2),\n    new GlideAdapter(valkey3),\n  ],\n})\n```\n\n### Logger Integration\n\nThe module supports external loggers for lock operations. Winston works directly, while Pino and Bunyan require adapters:\n\n#### Winston (Direct Support)\n\n```typescript\nimport * as winston from 'winston';\n\nconst logger = winston.createLogger({\n  level: 'info',\n  format: winston.format.json(),\n  transports: [new winston.transports.Console()],\n});\n\nRedlockModule.forRoot({\n  nodes: [new NodeRedisAdapter(redis1)],\n  logger, // Winston works directly\n})\n```\n\n#### Pino (Requires Adapter)\n\n```typescript\nimport pino from 'pino';\nimport { createPinoAdapter } from 'redlock-universal';\n\nconst pinoLogger = pino({ level: 'info' });\nconst logger = createPinoAdapter(pinoLogger);\n\nRedlockModule.forRoot({\n  nodes: [new NodeRedisAdapter(redis1)],\n  logger,\n})\n```\n\n#### Bunyan (Requires Adapter)\n\n```typescript\nimport * as bunyan from 'bunyan';\nimport { createBunyanAdapter } from 'redlock-universal';\n\nconst bunyanLogger = bunyan.createLogger({ name: 'myapp', level: 'info' });\nconst logger = createBunyanAdapter(bunyanLogger);\n\nRedlockModule.forRoot({\n  nodes: [new NodeRedisAdapter(redis1)],\n  logger,\n})\n```\n\n**Supported Loggers:**\n\n| Logger          | Works Directly | Adapter Needed               |\n| --------------- | -------------- | ---------------------------- |\n| Winston         | ✅ Yes         | No                           |\n| Pino            | ⚠️ Via Adapter | `createPinoAdapter()`        |\n| Bunyan          | ⚠️ Via Adapter | `createBunyanAdapter()`      |\n\n## API Reference\n\n### `@Redlock` Decorator\n\nAutomatically wraps a method with lock acquisition and release.\n\n```typescript\n@Redlock(options: RedlockDecoratorOptions)\n\ninterface RedlockDecoratorOptions {\n  // Static key or function that generates key from arguments\n  key: string | ((...args: unknown[]) =\u003e string);\n  // Lock time-to-live in milliseconds (default: module defaultTtl)\n  ttl?: number;\n  // Number of retry attempts (default: module retryAttempts)\n  retryAttempts?: number;\n  // Delay between retries in milliseconds (default: module retryDelay)\n  retryDelay?: number;\n}\n```\n\n**Examples:**\n\n```typescript\n// Static key\n@Redlock({ key: 'global:config:update' })\nasync updateConfig() { }\n\n// Dynamic key from arguments\n@Redlock({ key: (id: string) =\u003e `resource:${id}:lock` })\nasync updateResource(id: string) { }\n\n// Custom TTL\n@Redlock({ key: 'long:operation', ttl: 300000 }) // 5 minutes\nasync longRunningTask() { }\n\n// Multiple arguments\n@Redlock({ key: (type: string, id: string) =\u003e `${type}:${id}:lock` })\nasync process(type: string, id: string) { }\n```\n\n### `RedlockService`\n\nInjectable service for programmatic lock management.\n\n#### `acquire(key: string, ttl?: number): Promise\u003cLockHandle\u003e`\n\nAcquire a lock manually. Returns a handle that must be passed to `release()`.\n\n```typescript\nconst handle = await redlockService.acquire('resource:123');\ntry {\n  // Critical section\n} finally {\n  await redlockService.release('resource:123', handle);\n}\n```\n\n#### `release(key: string, handle: LockHandle): Promise\u003cvoid\u003e`\n\nRelease a previously acquired lock using its handle.\n\n#### `using\u003cT\u003e(key: string, fn: (signal?: AbortSignal) =\u003e Promise\u003cT\u003e, ttl?: number): Promise\u003cT\u003e`\n\nExecute a function with automatic lock management. **Recommended for most use cases.**\n\n```typescript\nconst result = await redlockService.using('resource:123', async (signal) =\u003e {\n  // Lock is automatically acquired, extended, and released\n\n  // Optional: Check if lock extension failed\n  if (signal?.aborted) {\n    throw new Error('Lock lost during operation');\n  }\n\n  return processResource();\n});\n```\n\n## Advanced Usage\n\n### Accessing Advanced Features\n\nFor advanced features like batch operations, health checks, and metrics, import directly from `redlock-universal`:\n\n```typescript\nimport { RedlockService } from 'nestjs-redlock-universal';\nimport { LockManager, HealthChecker, MetricsCollector } from 'redlock-universal';\n\n@Injectable()\nexport class AdvancedService {\n  constructor(private readonly redlock: RedlockService) {}\n\n  async batchOperations() {\n    // Use redlock-universal directly for batch locks\n    const manager = new LockManager({ nodes: [...] });\n    const handles = await manager.acquireBatch(['key1', 'key2', 'key3']);\n    // ... process\n    await manager.releaseBatch(handles);\n  }\n}\n```\n\n### Single-Node Setup (Development)\n\nFor development or single-instance deployments:\n\n```typescript\nimport { RedlockModule, NodeRedisAdapter } from 'nestjs-redlock-universal';\nimport { createClient } from 'redis';\n\nconst redis = createClient({ url: 'redis://localhost:6379' });\nawait redis.connect();\n\nRedlockModule.forRoot({\n  nodes: [new NodeRedisAdapter(redis)],\n  // Automatically uses SimpleLock instead of RedLock for single node\n})\n```\n\n### Lock Key Best Practices\n\n```typescript\n// ✅ Good: Specific, hierarchical keys\n@Redlock({ key: (userId) =\u003e `user:${userId}:profile:update` })\n\n// ✅ Good: Include resource type\n@Redlock({ key: (orderId) =\u003e `order:${orderId}:payment` })\n\n// ❌ Bad: Too generic\n@Redlock({ key: 'update' })\n\n// ❌ Bad: No namespace\n@Redlock({ key: (id) =\u003e id })\n```\n\n## Lock Strategy Selection\n\nThe module automatically selects the optimal lock strategy:\n\n- **1-2 nodes**: Uses `SimpleLock` (single-instance locking)\n- **3+ nodes**: Uses `RedLock` (distributed Redlock algorithm)\n\nFor production deployments, always use **3 or more Redis instances** for proper fault tolerance.\n\n## Testing\n\n### Mocking in Tests\n\n```typescript\nimport { Test } from '@nestjs/testing';\nimport { RedlockService } from 'nestjs-redlock-universal';\n\nconst module = await Test.createTestingModule({\n  providers: [\n    YourService,\n    {\n      provide: RedlockService,\n      useValue: {\n        using: vi.fn((key, fn) =\u003e fn()),\n        acquire: vi.fn(),\n        release: vi.fn(),\n      },\n    },\n  ],\n}).compile();\n```\n\n### Integration Testing\n\nSee [TESTING.md](./TESTING.md) for complete integration testing guide with Docker.\n\n## Performance\n\nBased on `redlock-universal` benchmarks:\n\n- **Acquisition latency**: \u003c1ms mean (P95: \u003c2ms)\n- **Throughput**: 3,300+ ops/sec (single node)\n- **Batch operations**: 500+ ops/sec (10-lock batches)\n- **Memory**: \u003c2KB per lock operation\n\n## Common Use Cases\n\n### 1. Prevent Duplicate Processing\n\n```typescript\n@Redlock({ key: (jobId) =\u003e `job:${jobId}:process` })\nasync processJob(jobId: string) {\n  // Ensures only one worker processes this job\n}\n```\n\n### 2. Exclusive Resource Access\n\n```typescript\n@Redlock({ key: (userId) =\u003e `user:${userId}:wallet` })\nasync updateWallet(userId: string, amount: number) {\n  // Prevents race conditions in balance updates\n}\n```\n\n### 3. Rate Limiting Critical Operations\n\n```typescript\n@Redlock({ key: 'api:external:call', ttl: 1000 })\nasync callRateLimitedAPI() {\n  // Ensures max 1 call per second across all instances\n}\n```\n\n### 4. Coordinated Cache Invalidation\n\n```typescript\n@Redlock({ key: 'cache:rebuild' })\nasync rebuildCache() {\n  // Only one instance rebuilds cache at a time\n}\n```\n\n## Troubleshooting\n\n### Lock Acquisition Fails\n\n**Problem**: `LockAcquisitionError: Failed to acquire lock`\n\n**Solutions**:\n- Check Redis connectivity: Ensure all nodes are reachable\n- Verify quorum settings: Need majority of nodes (or configured quorum)\n- Check lock contention: Another process may hold the lock\n- Increase retry attempts or delay in configuration\n\n### Lock Released Too Early\n\n**Problem**: Lock expires during long operation\n\n**Solutions**:\n- Use `using()` method instead of manual `acquire()`/`release()` - it auto-extends\n- Increase `defaultTtl` in module configuration\n- Check if operation can be split into smaller atomic operations\n\n### Memory Leaks\n\n**Problem**: Memory usage grows over time\n\n**Solutions**:\n- Ensure proper module cleanup (we handle this automatically via `onModuleDestroy`)\n- Check for uncaught errors that prevent lock release\n- Use `using()` method to guarantee cleanup\n\n### Module Not Initializing\n\n**Problem**: `LockManager not initialized` error\n\n**Solutions**:\n- Ensure Redis clients are connected before module initialization\n- Check for errors in `forRootAsync` factory function\n- Verify `onModuleInit` lifecycle hook completes successfully\n\nFor more help, see:\n- [redlock-universal documentation](https://www.npmjs.com/package/redlock-universal)\n- [Report an issue](https://github.com/alexpota/nestjs-redlock-universal/issues)\n\n## Why nestjs-redlock-universal?\n\n### vs. Raw Redis SET NX\n\n❌ Manual lock release\n❌ No automatic extension\n❌ No distributed consensus\n❌ Race conditions in cleanup\n\n✅ Automatic lifecycle management\n✅ Auto-extension for long operations\n✅ Distributed locking with quorum\n✅ Enhanced error handling\n\n### vs. Other NestJS Redis Libraries\n\nMost NestJS Redis libraries focus on caching. This library is purpose-built for **distributed locking**:\n\n- ✅ Redlock algorithm implementation\n- ✅ Automatic lock extension via `using()`\n- ✅ NestJS decorator for zero-boilerplate\n- ✅ Built on `redlock-universal`\n- ✅ Universal Redis/Valkey client support (node-redis, ioredis, Valkey GLIDE)\n\n## License\n\nMIT\n\n## Related Projects\n\n- [redlock-universal](https://www.npmjs.com/package/redlock-universal) - The underlying distributed lock library\n\n## Contributing\n\nIssues and pull requests are welcome! Please see our [contributing guidelines](./CONTRIBUTING.md).\n\n## Support\n\n- [Report bugs](https://github.com/alexpota/nestjs-redlock-universal/issues)\n- [Request features](https://github.com/alexpota/nestjs-redlock-universal/issues)\n- [Documentation](https://github.com/alexpota/nestjs-redlock-universal#readme)\n- [Star on GitHub](https://github.com/alexpota/nestjs-redlock-universal)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexpota%2Fnestjs-redlock-universal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexpota%2Fnestjs-redlock-universal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexpota%2Fnestjs-redlock-universal/lists"}