Skip to content

Recipes

Common use cases and real-world examples.

1. Payment Processing

typescript
import { Injectable, ConflictException } from '@nestjs/common';
import { WithLock } from '@nestjs-redisx/locks';
import { Payment, OrderRepository, PaymentGateway } from '../types';

@Injectable()
export class PaymentService {
  constructor(
    private readonly orders: OrderRepository,
    private readonly gateway: PaymentGateway,
  ) {}

  @WithLock({ key: 'payment:order:{0}', ttl: 30000 })
  async processPayment(orderId: string): Promise<Payment> {
    const order = await this.orders.findOne(orderId);

    if (order.status === 'paid') {
      throw new ConflictException('Already paid');
    }

    const payment = await this.gateway.charge(order.amount);
    await this.orders.update(orderId, { status: 'paid', paymentId: payment.id });

    return payment;
  }
}

2. Inventory Management

typescript
import { Injectable } from '@nestjs/common';
import { WithLock } from '@nestjs-redisx/locks';
import { InventoryStore } from '../types';

@Injectable()
export class InventoryService {
  constructor(private readonly inventory: InventoryStore) {}

  @WithLock({ key: 'inventory:{0}', ttl: 5000 })
  async reserveStock(sku: string, quantity: number): Promise<boolean> {
    const current = await this.inventory.getStock(sku);

    if (current < quantity) {
      return false;
    }

    await this.inventory.decrement(sku, quantity);
    return true;
  }
}

3. Data Synchronization

typescript
import { Injectable } from '@nestjs/common';
import { WithLock } from '@nestjs-redisx/locks';
import { ExternalApiClient, Database } from '../types';

@Injectable()
export class SyncService {
  constructor(
    private readonly externalApi: ExternalApiClient,
    private readonly db: Database,
  ) {}

  @WithLock({
    key: 'sync:products',
    ttl: 300000, // 5 min
    autoRenew: true,
  })
  async syncProducts(): Promise<void> {
    // Only one instance syncs at a time
    const products = await this.externalApi.fetchProducts();
    await this.db.bulkUpsert(products);
  }
}

4. Leader Election

typescript
import { Injectable, Inject, OnModuleInit } from '@nestjs/common';
import { LOCK_SERVICE, ILockService } from '@nestjs-redisx/locks';

@Injectable()
export class SchedulerService implements OnModuleInit {
  private running = true;

  constructor(
    @Inject(LOCK_SERVICE) private readonly lockService: ILockService,
  ) {}

  async onModuleInit() {
    this.tryBecomeLeader();
  }

  private async tryBecomeLeader() {
    while (this.running) {
      try {
        await this.lockService.withLock(
          'leader:scheduler',
          async () => {
            console.log('I am the leader!');
            await this.runScheduledJobs();
          },
          { ttl: 60000, autoRenew: true },
        );
      } catch {
        // Not the leader, wait and try again
        await this.sleep(30000);
      }
    }
  }

  private async runScheduledJobs() { /* process jobs */ }
  private sleep(ms: number) { return new Promise((r) => setTimeout(r, ms)); }
}

5. Job Processing

typescript
import { Injectable, Inject } from '@nestjs/common';
import { LOCK_SERVICE, ILockService } from '@nestjs-redisx/locks';
import { JobQueue } from '../types';

@Injectable()
export class WorkerService {
  constructor(
    @Inject(LOCK_SERVICE) private readonly lockService: ILockService,
    private readonly queue: JobQueue,
  ) {}

  async processNextJob(): Promise<boolean> {
    const job = await this.queue.peek();
    if (!job) return false;

    const lock = await this.lockService.tryAcquire(`job:${job.id}`);
    if (!lock) {
      return false; // Another worker claimed it
    }

    try {
      await this.execute(job);
      await this.queue.complete(job.id);
      return true;
    } finally {
      await lock.release();
    }
  }

  private async execute(job: any) { /* process job */ }
}

6. Rate-Limited Resource

typescript
import { Injectable } from '@nestjs/common';
import { WithLock } from '@nestjs-redisx/locks';
import { Data, ExternalApiClient } from '../types';

@Injectable()
export class ExternalApiService {
  constructor(private readonly externalApi: ExternalApiClient) {}

  @WithLock({
    key: 'api:ratelimit',
    ttl: 1000, // 1 second window
    onLockFailed: 'skip',
  })
  async callRateLimitedApi(): Promise<Data> {
    // Only allow one call per second globally
    return this.externalApi.call();
  }
}

Next Steps

Released under the MIT License.