mcpField
T
tl-pg-boss

tl-pg-boss

github.com/toddlevyDevelopment146 installsUpdated 2w ago
Repository

About tl-pg-boss

tl-pg-boss is a skill for setting up PostgreSQL-backed background job queues in Node.js applications. It covers scheduling, retries, typed handlers, and operational practices, including transactional job creation.

Key features

  1. 01PostgreSQL-backed job queues
  2. 02Cron and delayed scheduling
  3. 03Retries and job expiration
  4. 04Batch and typed workers
  5. 05Dead-letter queues and retention
  6. 06Monitoring with dashboard and SQL

Use cases

  1. 01Add background processing to a Node.js app
  2. 02Schedule recurring or delayed tasks
  3. 03Retry failed work using PostgreSQL
  4. 04Monitor pending and failed jobs

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/toddlevy/tl-agent-skills --skill tl-pg-boss

After installing, type/tl-pg-bossin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

LICENSE
README.md
references/
advanced-patterns.md
kubernetes-deployment.md
monitoring.md
observability.md
schedule-management.md
send-options.md
testing-patterns.md
typescript-patterns.md
wordpress-migration.md

Details

From the project README

tl-pg-boss

PostgreSQL-backed job queue for Node.js with exactly-once delivery, cron scheduling, and transactional safety.

When to Use

  • "Add background jobs to my app"
  • "I need a job queue but already use Postgres"
  • "Set up cron jobs / scheduled tasks"
  • "Process async work with retries"
  • User mentions: job queue, background processing, task scheduling, worker
When to Consider Alternatives

For managed background jobs with dashboard UI, consider Trigger.dev instead. pg-boss is best when:

  • You want self-hosted, PostgreSQL-native job queues
  • You already have Postgres and don't want external dependencies
  • You need transactional job enqueuing (same transaction as your data writes)
  • Cost matters (pg-boss is free, managed services charge per job)

Trigger.dev is better when:

  • You need a polished dashboard out of the box
  • You want managed infrastructure with auto-scaling
  • Your team prefers a hosted SaaS experience
  • You need complex workflows with visual orchestration

Outcomes

  • Patch: pg-boss installed and configured with typed job handlers
  • Artifact: Job definitions with proper queue setup
  • Decision: Queue structure, job patterns, monitoring approach

Requirements

RequirementVersion
Node.js22.12+
PostgreSQL13+
PrivilegeCREATE on database

Installation

pnpm add pg-boss

Auto-creates pgboss schema on first start(). No manual migrations needed.


Core Concepts

SKIP LOCKED

PostgreSQL's SKIP LOCKED provides exactly-once delivery without distributed transactions.

Always Call start()

Critical: Every process must call start(), even producers.

const boss = new PgBoss(connectionString);
await boss.start(); // Required in EVERY process

Even with multiple processes calling start(), only one runs supervision.

One Queue Per Job Type
await boss.createQueue("send-email");
await boss.createQueue("process-image");

Basic Setup

import { PgBoss } from "pg-boss";

const boss = new PgBoss({
  connectionString: process.env.DATABASE_URL,
  schema: "pgboss",
});

boss.on("error", console.error);
await boss.start();
await boss.createQueue("my-queue");

Job Patterns

Send a Job
const jobId = await boss.send("my-queue", { userId: "123" });
Send with Options
await boss.send("my-queue", payload, {
  retryLimit: 3,
  retryDelay: 60,
  expireInMinutes: 30,
  priority: 1,
});

Full options: See references/send-options.md

Delayed Job
await boss.send("my-queue", payload, {
  startAfter: new Date(Date.now() + 60000),
});
Cron Scheduling
await boss.schedule("daily-report", "0 9 * * *", { type: "daily" });
Unschedule (Remove Cron)
await boss.unschedule("daily-report");

Schedule management: See references/schedule-management.md


Worker Patterns

Basic Worker
await boss.work("my-queue", async ([job]) => {
  console.log(`Processing ${job.id}`);
  // Auto-completes on return, throw to fail
});

Note: Callback receives an array even with batchSize: 1.

Batch Processing
await boss.work("bulk-import", { batchSize: 10 }, async (jobs) => {
  for (const job of jobs) {
    await processItem(job.data);
  }
});
Typed Jobs
interface EmailJob {
  to: string;
  subject: string;
}

await boss.send<EmailJob>("send-email", { to: "[email protected]", subject: "Hi" });

await boss.work<EmailJob>("send-email", async ([job]) => {
  await sendEmail(job.data.to, job.data.subject);
});

TypeScript patterns: See references/typescript-patterns.md


Queue Configuration

Dead Letter Queue
await boss.createQueue("my-queue", { deadLetter: "my-queue-dlq" });
await boss.createQueue("my-queue-dlq");
Retention
await boss.createQueue("my-queue", { retentionMinutes: 60 * 24 });

Fastify Integration

import Fastify from "fastify";
import { PgBoss } from "pg-boss";

const fastify = Fastify();
const boss = new PgBoss(process.env.DATABASE_URL);

fastify.decorate("boss", boss);

fastify.addHook("onReady", async () => {
  boss.on("error", fastify.log.error.bind(fastify.log));
  await boss.start();
  await boss.createQueue("my-queue");
  await boss.work("my-queue", handler);
});

fastify.addHook("onClose", async () => {
  await boss.stop({ graceful: true });
});

Monitoring

Dashboard
pnpm add @pg-boss/dashboard
DATABASE_URL="postgres://..." npx pg-boss-dashboard
Quick SQL
-- Pending by queue
SELECT name, COUNT(*) FROM pgboss.job WHERE state = 'created' GROUP BY name;

-- Failed jobs
SELECT * FROM pgboss.job WHERE state = 'failed' ORDER BY completedon DESC LIMIT 20;

Full monitoring: See references/monitoring.md


Sharp Edges

GotchaSolution
Must call start() everywhereEven producers need it
Jobs array in workerUse ([job]) not (job)
No LISTEN/NOTIFYPolling only, set pollingIntervalSeconds
Schema needs CREATE privilegeOr use CLI: npx pg-boss migrate --dry-run
Once completed, can't failDon't mix work() with manual fail()
No pauseQueue() in v10Use unschedule() + direct SQL
Schedules re-register on restartCode calls schedule() on init; unschedule is temporary

Best Practices

  1. Set expiration to prevent zombies: expireInMinutes: 30
  2. Archive aggressively with retention policies
  3. Idempotent handlers using upserts
  4. Graceful shutdown: boss.stop({ graceful: true })

Observability

See Observability for Prometheus gauge/counter setup with prom-client and Prometheus alerting rules for queue backlog and failure rate.


Testing Patterns

See Testing Patterns for unit testing job handlers, integration testing with a real PostgreSQL database, and mocking pg-boss in vitest.


Kubernetes Deployment

See Kubernetes Deployment for a worker Deployment manifest with resource limits, liveness probe, preStop hook, and the SIGTERM graceful-shutdown handler.


Verification

  1. boss.start() completes without error
  2. boss.createQueue() succeeds
  3. boss.send() returns a job ID
  4. Worker processes the job
  5. Job state changes to completed

References

Quilted Skills
First-Party Documentation
PostgreSQL Resources
Alternative Solutions
Skill References
  • references/send-options.md — Full send options
  • references/typescript-patterns.md — BaseJob, JobManager
  • references/monitoring.md — Dashboard + SQL queries
  • references/advanced-patterns.md — Singleton, throttling, pub/sub
  • references/schedule-management.md — Cron schedules, unschedule, pause patterns
  • references/wordpress-migration.md — WP-Cron & Action Scheduler mapping

Attribution

SourceAuthorContribution
pg-bossTim Gilbert (@timgit)Core library, official docs
TypeScript Deep DiveShayan (@ImSh4yy)BaseJob class, JobManager pattern
pg-boss-admin-dashboardLyubomir Petrov (@lpetrov)Alternative dashboard with JMESPath

Comments

Markdown supported