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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@develop-x/nest-lock

v1.0.6

Published

A NestJS Redis-based distributed lock and rate limiting library that provides both decorators and services for preventing concurrent execution and rate limiting in distributed applications.

Readme

@develop-x/nest-lock

A NestJS Redis-based distributed lock and rate limiting library that provides both decorators and services for preventing concurrent execution and rate limiting in distributed applications.

Features

  • Distributed Locking: Prevent concurrent execution of critical sections across multiple instances
  • Rate Limiting: Control the frequency of operations per user/key
  • Decorator Support: Easy-to-use decorators for method-level protection
  • Service Classes: Programmatic access to locking and rate limiting functionality
  • Redis Backend: Uses Redis for distributed coordination
  • TypeScript Support: Full TypeScript definitions included

Installation

npm install @develop-x/nest-lock

Dependencies

This library requires the following peer dependencies:

  • @nestjs/common ^11.0.1
  • @nestjs/core ^11.0.1

The following dependencies are included:

  • ioredis ^5.6.1 - Redis client
  • redlock ^5.0.0-beta.2 - Distributed locking implementation
  • rate-limiter-flexible ^7.1.1 - Rate limiting implementation

Setup

1. Import the Module

import { Module } from '@nestjs/common';
import { LockModule } from '@develop-x/nest-lock';

@Module({
  imports: [
    LockModule.forRootAsync({
      useFactory: () => ({
        host: 'localhost',
        port: 6379,
        // other Redis options
      }),
      inject: [], // optional dependencies
    }),
  ],
})
export class AppModule {}

2. Inject Services in Your Classes

import { Injectable } from '@nestjs/common';
import { RedisLockService, RedisLimitService } from '@develop-x/nest-lock';

@Injectable()
export class UserService {
  constructor(
    private readonly redisLockService: RedisLockService,
    private readonly redisLimitService: RedisLimitService,
  ) {}
}

Usage

Distributed Locking

Using the Decorator

import { RedisLock } from '@develop-x/nest-lock';

@Injectable()
export class PaymentService {
  constructor(private readonly redisLockService: RedisLockService) {}

  // Lock by specific key
  @RedisLock({
    key: 'payment-processing',
    ttl: 5000, // 5 seconds
  })
  async processPayment(paymentData: any) {
    // This method will be locked for 5 seconds
    // Only one instance can execute this at a time
  }

  // Lock by extracting values from arguments
  @RedisLock({
    keys: ['userId'], // Extract userId from the first argument object
    ttl: 3000,
  })
  async updateUserBalance(userData: { userId: string, amount: number }) {
    // Locked per userId - each user can have one concurrent update
  }

  // Lock with custom key function
  @RedisLock({
    keyFn: (orderId: string, userId: string) => `order:${orderId}:user:${userId}`,
    ttl: 10000,
    onError: () => {
      throw new Error('Order is being processed by another request');
    }
  })
  async processOrder(orderId: string, userId: string) {
    // Custom lock key generation with custom error handling
  }
}

Using the Service Directly

@Injectable()
export class InventoryService {
  constructor(private readonly redisLockService: RedisLockService) {}

  async updateStock(productId: string, quantity: number) {
    return await this.redisLockService.runWithLock(
      `product:${productId}`,
      5000, // 5 second TTL
      async () => {
        // Critical section - only one update per product at a time
        const currentStock = await this.getStock(productId);
        return await this.setStock(productId, currentStock + quantity);
      },
      () => {
        throw new Error('Product stock is being updated, please try again');
      }
    );
  }
}

Rate Limiting

Using the Service

@Injectable()
export class ApiService {
  constructor(private readonly redisLimitService: RedisLimitService) {}

  async createPost(userId: string, postData: any) {
    return await this.redisLimitService.runWithLimit(
      `user:${userId}:create-post`,
      async (rateInfo) => {
        // This will be executed if rate limit is not exceeded
        console.log(`Remaining requests: ${rateInfo.remainingPoints}`);
        return await this.savePost(postData);
      },
      {
        points: 5,           // 5 requests
        duration: 300,       // per 5 minutes (300 seconds)
        blockDuration: 600,  // block for 10 minutes if exceeded
        onLimitFail: (rejRes) => {
          throw new Error(`Rate limit exceeded. Try again in ${Math.round(rejRes.msBeforeNext / 1000)} seconds`);
        }
      }
    );
  }

  async sendMessage(userId: string, message: string) {
    return await this.redisLimitService.runWithLimit(
      `user:${userId}:send-message`,
      async () => {
        return await this.deliverMessage(message);
      },
      {
        points: 10,          // 10 messages
        duration: 60,        // per minute
        throwOnBlocked: false, // Return null instead of throwing
      }
    );
  }
}

Use Cases

1. Payment Processing

Prevent double-spending and concurrent payment processing:

@RedisLock({ keys: ['transactionId'], ttl: 30000 })
async processPayment(payment: { transactionId: string, amount: number }) {
  // Ensures only one payment with the same transactionId processes at a time
}

2. Inventory Management

Prevent overselling in high-concurrency scenarios:

@RedisLock({ keyFn: (productId) => `inventory:${productId}`, ttl: 5000 })
async purchaseProduct(productId: string, quantity: number) {
  // Prevents concurrent stock modifications for the same product
}

3. User Account Operations

Prevent concurrent modifications to user accounts:

@RedisLock({ keys: ['userId'], ttl: 10000 })
async updateUserProfile(user: { userId: string, data: any }) {
  // Ensures user profile updates are atomic per user
}

4. API Rate Limiting

Control API usage per user:

async handleApiRequest(userId: string) {
  return await this.redisLimitService.runWithLimit(
    `api:${userId}`,
    async () => {
      return await this.processRequest();
    },
    { points: 100, duration: 3600 } // 100 requests per hour
  );
}

5. Resource Allocation

Limit concurrent access to shared resources:

@RedisLock({ key: 'file-processor', ttl: 60000 })
async processLargeFile(filePath: string) {
  // Only allow one file processing operation at a time
}

6. Database Migration/Maintenance

Ensure only one maintenance operation runs:

@RedisLock({ key: 'db-maintenance', ttl: 300000 })
async runMaintenanceTask() {
  // Prevents multiple instances from running maintenance simultaneously
}

Configuration Options

RedisLock Decorator Options

| Option | Type | Description | Default | |--------|------|-------------|---------| | key | string | Static lock key | - | | keys | string[] | Extract values from method arguments to build key | - | | keyFn | function | Custom function to generate lock key | - | | ttl | number | Lock time-to-live in milliseconds | 3000 | | onError | function | Custom error handler when lock acquisition fails | - |

RedisLimitService Options

| Option | Type | Description | Default | |--------|------|-------------|---------| | points | number | Number of requests allowed | 5 | | duration | number | Time window in seconds | 300 | | blockDuration | number | Block duration in seconds when limit exceeded | 600 | | throwOnBlocked | boolean | Whether to throw exception or return null | true | | onLimitFail | function | Custom handler when rate limit is exceeded | - |

Error Handling

The library throws HTTP exceptions with status code 429 (Too Many Requests) when:

  • Lock acquisition fails (concurrent access detected)
  • Rate limit is exceeded

You can customize error handling using the onError and onLimitFail options.

Best Practices

  1. Choose appropriate TTL values: Set lock TTL slightly longer than expected operation time
  2. Use meaningful lock keys: Include relevant identifiers to scope locks appropriately
  3. Handle lock failures gracefully: Provide user-friendly error messages
  4. Monitor Redis performance: High lock contention may indicate design issues
  5. Use rate limiting for public APIs: Protect against abuse and ensure fair usage
  6. Combine with circuit breakers: For additional resilience in distributed systems

License

This library is part of the @develop-x namespace and follows the project's licensing terms.