nestjs-pg-lock
v0.1.0
Published
Distributed lock for NestJS using PostgreSQL Advisory Locks
Maintainers
Readme
🔒 nestjs-pg-lock
An enterprise-grade, distributed locking library for NestJS utilizing PostgreSQL Advisory Locks.
Built with scalability, safety, and operational excellence in mind, nestjs-pg-lock prevents race conditions and ensures safe concurrency control in distributed environments (microservices, multi-instance deployments)—all without the infrastructure overhead of Redis.
✨ Key Features
- AOP Based Lifecycle: Locks are strictly acquired and safely released via standard NestJS interceptors (
rxjs). - Dynamic Lock Keys: Bind lock keys dynamically using
ExecutionContext(e.g.,req.user.id,body.itemId). - Fast Fail Mechanism: Instantly rejects conflicting requests throwing a dedicated
PgLockConflictException(HTTP 409) rather than causing server deadlocks or infinite holding. - Safe Pool Management: Manages its own dedicated
pg.Poolto bypass ORM (Prisma/TypeORM) connection-swapping bottlenecks, entirely eliminating connection leaks. - Robust Hashing: Safely compresses long dynamic strings into PostgreSQL-compatible 32-bit integer limits using internal SHA-256 hashing.
🚀 Installation
npm install nestjs-pg-lock pg(Ensure you have PostgreSQL installed. The library leverages standard pg as a peer dependency.)
🛠 Usage
1. Module Registration
Register the PgLockModule globally in your Application, providing standard PostgreSQL pool credentials.
import { Module } from '@nestjs/common';
import { PgLockModule } from 'nestjs-pg-lock';
@Module({
imports: [
// Sync setup
PgLockModule.forRoot({
host: 'localhost',
port: 5432,
user: 'postgres',
password: 'password',
database: 'my_db',
max: 5, // Dedicated lock pool size
connectionTimeoutMillis: 5000,
}),
// OR Async setup via ConfigService
PgLockModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
connectionString: config.get('DATABASE_URL'),
}),
}),
]
})
export class AppModule {}2. Method Decorator
Attach the @UsePgLock decorator to any NestJS controllers or providers.
Static Lock Key
Locks all incoming requests attempting to execute the method globally.
import { UsePgLock } from 'nestjs-pg-lock';
@Post('global-sync')
@UsePgLock('global-sync-job')
async triggerGlobalSync() {
// Only one instance/thread worldwide can run this concurrently.
}Dynamic Lock Key (Contextual)
Create highly granular locks by evaluating the runtime ExecutionContext.
@Post('order')
@UsePgLock((ctx) => `checkout-user-${ctx.switchToHttp().getRequest().user.id}`)
async checkout() {
// A specific user cannot trigger multiple checkouts at once.
// However, different users can checkout in parallel safely!
}3. Exception Handling (Fast Fail)
If a lock is already held by another process, the interceptor immediately rejects the incoming traffic.
Catch the standard PgLockConflictException in your global filters for graceful API responses:
import { Catch, ExceptionFilter, ArgumentsHost } from '@nestjs/common';
import { PgLockConflictException } from 'nestjs-pg-lock';
@Catch(PgLockConflictException)
export class LockConflictFilter implements ExceptionFilter {
catch(exception: PgLockConflictException, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse();
response.status(409).json({
statusCode: 409,
message: 'The requested resource is currently locked and processing. Please try again soon.',
error: 'Conflict',
});
}
}⚠️ Important Infrastructure Notice (PgBouncer)
PostgreSQL Advisory Locks are strictly session-level (bound to the physical connection).
If your architecture relies on a database connection pooler like PgBouncer or AWS RDS Proxy operating in Transaction Pooling Mode, session-level state will be dropped or swapped across clients.
Solution: The PgLockModule connection must bypass transaction poolers. Provide a direct connection string (Bypass Port) pointing strictly to your physical DB instance for this module.
