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
- Troubleshooting — Debug common issues
- Overview — Back to overview