nestjs-pgmq is a robust PostgreSQL Message Queue (PGMQ) integration for NestJS. It allows you to build distributed background processing systems using your existing PostgreSQL database β without introducing additional infrastructure like Redis or RabbitMQ.
The developer experience is heavily inspired by @nestjs/bull, making migration simple and intuitive.
- π Zero Infrastructure Overhead β Uses your existing PostgreSQL instance
- π¦ Bull-like API β Familiar decorators:
@Processor,@Process,@InjectQueue - π‘ Transactional Safety β Supports the Transactional Outbox pattern (WIP)
- β‘ High Performance β Uses
SKIP LOCKEDfor safe concurrent processing - π Observability β Automatic
correlationId,producerId, timestamps - π Dead Letter Queues (DLQ) β Failed jobs stored with full stack traces
- π Graceful Shutdown β No jobs lost during deploys or restarts
This library requires PostgreSQL with the pgmq extension installed.
docker run -d \
--name pgmq \
-p 5432:5432 \
-e POSTGRES_PASSWORD=postgres \
ghcr.io/pgmq/pg18-pgmq:v1.7.0CREATE EXTENSION IF NOT EXISTS pgmq CASCADE;pnpm add nestjs-pgmq pg
# or
npm install nestjs-pgmq pgImport PgmqModule in your root module and configure the database connection.
// src/app.module.ts
import { Module } from '@nestjs/common';
import { PgmqModule } from 'nestjs-pgmq';
@Module({
imports: [
PgmqModule.forRootAsync({
useFactory: () => ({
connectionString: 'postgres://postgres:postgres@localhost:5432/db',
}),
}),
PgmqModule.registerQueue({
name: 'notifications',
}),
],
})
export class AppModule {}Define a processor using @Processor and handle jobs using @Process.
// src/notifications.processor.ts
import { Processor, Process, PgmqJob } from 'nestjs-pgmq';
@Processor('notifications')
export class NotificationsProcessor {
@Process('send-email')
async handleEmail(job: PgmqJob<{ email: string; body: string }>) {
console.log(`Sending email to ${job.data.email}...`);
// If this throws, the job is retried
// On success, the job is archived
}
}// src/users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectQueue, PgmqQueue } from 'nestjs-pgmq';
@Injectable()
export class UsersService {
constructor(
@InjectQueue('notifications')
private readonly queue: PgmqQueue,
) {}
async registerUser(email: string) {
await this.queue.add('send-email', {
email,
body: 'Welcome to our platform!',
});
}
}Achieve full data consistency by committing both your domain data and queue job in a single database transaction.
If the transaction rolls back, the job is never scheduled.
Supported ORMs: TypeORM, Drizzle (via adapter)
await this.dataSource.transaction(async (manager) => {
const user = await manager.save(User, { email: 'test@example.com' });
await this.queue.add(
'send-welcome',
{ userId: user.id },
{ connection: manager } // Atomicity guaranteed
);
});π See the examples/ folder for full implementations with Drizzle ORM and TypeORM.
Every message is automatically enriched with metadata headers:
correlationIdβ unique trace ID (UUID)messageIdβ unique message IDproducerIdβ hostname + PIDappVersionβ frompackage.jsoncreatedAtβ timestamp
You can override headers if needed:
await this.queue.add(
'process-order',
{ orderId: 123 },
{ correlationId: 'req-abc-123' }
);The module uses an Envelope Pattern for error handling.
Flow:
- Job fails β retried after visibility timeout (default: 30s)
- Exceeds max retries (default: 5)
- Moved to
<queue_name>_dlq
DLQ Message Example:
{
"headers": {
"errorType": "Error",
"errorMessage": "Connection timeout",
"stackTrace": "Error: Connection timeout\n at EmailService.send...",
"retryCount": 5,
"failedAt": "2023-10-25T12:00:00Z",
"originalQueue": "notifications"
},
"body": {
"jobName": "send-email",
"data": {
"email": "user@example.com"
}
}
}Workers use intelligent polling with SKIP LOCKED and batch processing to maximize throughput while maintaining safety.
This project is a pnpm monorepo.
# install deps
pnpm install
# run example
cd examples/basic-app
pnpm start:dev
# watch library changes
cd packages/nestjs-pgmq
pnpm build --watchMIT