Node.js + pg-boss in 2026: Postgres Job Queues at Scale
Every non-trivial Node.js backend eventually needs to do work outside the request/response cycle: send emails, generate PDFs, sync third-party data, retry failed webhooks, run nightly reports. The default answer for years has been "add Redis and reach for BullMQ." But in 2026 a quieter pattern is winning real production workloads: if your data already lives in Postgres, you can run a durable, transactional job queue inside the very same database — no second datastore, no extra moving part to monitor, back up, and pay for.
pg-boss is the library that makes this practical. It turns a Postgres instance into a full-featured queue using the database's own SKIP LOCKED row locking, giving you atomic enqueue-with-your-transaction semantics, retries with backoff, scheduling, dead-letter handling, and effectively-once processing. This guide walks through how it works under the hood, how to wire it up for production, how it compares to BullMQ and RabbitMQ, and when you should — and shouldn't — choose it.
Why Postgres-Native Job Queues Are Having a Moment in 2026
The industry mood has shifted away from sprawling infrastructure. Teams that spent the late 2010s adding Redis, RabbitMQ, Kafka, and a separate scheduler to every service are now consolidating. The reasoning is simple: each additional stateful service is another thing to provision, secure, patch, monitor, back up, and reason about during incidents. For a large share of applications, a single well-tuned Postgres instance can do the job of three of those boxes.
One datastore, one backup, one mental model
When your jobs live in Postgres, they are covered by the same backups, the same point-in-time recovery, the same connection pooling, and the same observability you already run for your relational data. There is no risk of your queue and your database drifting out of sync after a crash, because they are the same system. This is the headline reason mid-sized teams adopt pg-boss — operational surface area drops dramatically.
Transactional enqueue is the killer feature
With an external broker, enqueueing a job and committing your database transaction are two separate operations that can fail independently — the classic dual-write problem. With pg-boss you can insert the job in the same transaction that writes your business data, so either both happen or neither does. That single guarantee eliminates a whole category of "the order was saved but the confirmation email never queued" bugs. If you are designing backend systems where this matters, it is the kind of detail a seasoned engineer will flag immediately — and exactly what you should probe for when you hire a backend developer.

How pg-boss Works Under the Hood: SKIP LOCKED
The magic that makes a relational table behave like a real queue is a single SQL clause: FOR UPDATE SKIP LOCKED. When a worker polls for the next job, Postgres locks the candidate rows it returns and silently skips any rows already locked by another worker. That means dozens of workers can hammer the same queue table concurrently without ever handing the same job to two consumers — no application-level coordination required.
Atomic commits and effectively-once delivery
Because the fetch, the lock, and the state transition all happen inside a Postgres transaction, a job is only marked active once and is only marked completed when the transaction commits. If a worker crashes mid-job, the lock is released when its connection dies and the job becomes available again after its expiration window. The result is effectively-once processing: jobs are not lost, and they are not silently delivered twice under normal operation.
What pg-boss adds on top of the table
pg-boss manages its own schema (a set of tables and indexes in a dedicated namespace), handles archiving of completed jobs, maintains scheduled and singleton jobs, and exposes a clean async API so you never write the SQL yourself. Version 10 and later introduced explicit queue creation, richer queue storage policies for rate limiting and debouncing, and a CLI for running schema migrations in CI/CD pipelines.
Getting Started: Your First Producer and Worker
Installation is a single npm install pg-boss and a Postgres connection string. On first start, pg-boss provisions its schema automatically. The example below shows the two halves of any queue system — a producer that enqueues work and a worker that consumes it — wired with the retry, backoff, batching, and concurrency options you actually want in production.
import PgBoss from 'pg-boss';
const boss = new PgBoss({
connectionString: process.env.DATABASE_URL,
// keep completed jobs around for 7 days for auditing/debugging
archiveCompletedAfterSeconds: 60 * 60 * 24 * 7,
retentionDays: 7,
});
boss.on('error', (err) => console.error('[pg-boss]', err));
async function main() {
await boss.start();
const QUEUE = 'send-welcome-email';
await boss.createQueue(QUEUE); // idempotent in pg-boss v10+
// Producer: enqueue a job with retries and exponential backoff
await boss.send(QUEUE, { userId: 42, email: 'dev@example.com' }, {
retryLimit: 5,
retryBackoff: true, // 2s, 4s, 8s, ... between attempts
expireInSeconds: 120, // mark as failed if a worker holds it too long
});
// Consumer: process up to 10 jobs concurrently, in batches of 5
await boss.work(QUEUE, { batchSize: 5, teamConcurrency: 10 }, async (jobs) => {
for (const job of jobs) {
await sendEmail(job.data.email);
// throwing here triggers pg-boss retry/backoff automatically
}
});
}
async function sendEmail(to) {
// ... your transactional email logic
}
main().catch((e) => { console.error(e); process.exit(1); });A few things to notice. retryBackoff: true gives you exponential spacing between attempts so a struggling downstream service is not hammered. teamConcurrency caps how many jobs a single worker process runs at once, and batchSize controls how many it fetches per poll — together they let you tune throughput against the load your handlers place on the rest of the system. The interactive chart below puts pg-boss throughput in context against Redis- and broker-based queues.
Production Patterns: Retries, Scheduling, and Dead-Letter Queues
A toy queue accepts jobs and runs them. A production queue survives bad input, flaky dependencies, and 3 a.m. outages. pg-boss ships the primitives for all three out of the box, which is a big part of why teams pick it over hand-rolled solutions.
Retries with backoff and dead-letter queues
Hire Pre-Vetted Node.js Developers
Skip the months-long search. Our exclusive talent network has senior Node.js experts ready to join your team in 48 hours.
Set retryLimit and retryBackoff per job or per queue, and pg-boss will re-attempt failed work on an exponential schedule. Once a job exhausts its retries, pg-boss can move it to a dead-letter queue you nominate, so failures are captured for inspection and manual replay instead of vanishing. This mirrors the dead-letter pattern of dedicated brokers without any of the broker.
Cron scheduling and singleton jobs
pg-boss has a built-in scheduler: boss.schedule('nightly-report', '0 3 * * *', data) runs a job on a cron expression, persisted in Postgres so it survives restarts and works across a cluster without two nodes firing the same tick. Singleton and throttled jobs let you guarantee only one instance of a task runs in a given window — ideal for syncs and report generation.

pg-boss vs BullMQ vs RabbitMQ: Choosing the Right Queue
The honest framing is not "which queue is best" but "which trade-offs fit your system." BullMQ is Redis-backed and excels at very high throughput and rich rate-limiting, at the cost of running and securing a Redis cluster. RabbitMQ is a battle-tested broker with sophisticated routing and strong cross-language support, but it is another distributed system to operate. pg-boss trades peak throughput for radical operational simplicity and transactional enqueue. The radar chart below scores the three across the dimensions that usually decide the call.
A simple decision rule
If your application already runs Postgres and your job volume is comfortably under a few thousand per second, pg-boss is almost certainly the right default — it removes infrastructure rather than adding it. If you are pushing tens of thousands of jobs per second, need sub-millisecond pickup, or have many languages publishing to the same bus, a dedicated broker earns its keep. Most products never cross that line.
Scaling, Monitoring, and Operational Best Practices
Scaling pg-boss is mostly about treating Postgres well. Because workers poll, the queue's ceiling is your database's capacity to serve those queries cheaply, so the operational playbook is the familiar one for any Postgres-heavy app.
Connection pooling and worker fan-out
Every worker process holds Postgres connections, so put a pooler like PgBouncer in front and keep per-process concurrency sensible — a hundred workers each opening ten direct connections will exhaust a default Postgres max_connections fast. Tune polling intervals so idle queues are not generating needless load. Getting this balance right is squarely Postgres expertise territory, and it pays to have someone on the team who has tuned it before.
Observability and the dashboard
Because jobs are rows, you get observability for free: SELECT against the pg-boss tables to see queue depth, failure rates, and stuck jobs, or wire those queries into Grafana. The official @pg-boss/dashboard package adds a web UI for monitoring queues, schedules, and individual jobs, which closes the gap with the management consoles that ship with RabbitMQ and BullMQ.
Finally, archive aggressively. pg-boss can move completed jobs into archive tables and purge them on a schedule; leaving millions of finished rows in the active table slowly degrades query performance. Set archiveCompletedAfterSeconds and retentionDays to values that match your auditing needs and let the library keep the hot table small.
Designing a reliable queue layer — picking delivery guarantees, sizing worker pools, getting the Postgres tuning right — is exactly the kind of work that separates a senior engineer from a junior one. If you are building a Node.js system and want that expertise on your team fast, HireNodeJS connects you with pre-vetted senior developers available within 48 hours, without the recruiter overhead.
Hire Expert Node.js Developers — Ready in 48 Hours
Building the right system is only half the battle — you need the right engineers to build it. HireNodeJS.com specialises exclusively in Node.js talent: every developer is pre-vetted on real-world projects, API design, event-driven architecture, and production deployments.
Unlike generalist platforms, our curated pool means you speak only to engineers who live and breathe Node.js. Most clients have their first developer working within 48 hours of getting in touch. Engagements start as short-term contracts and can convert to full-time hires with zero placement fee.
Conclusion: When pg-boss Is the Right Call
pg-boss represents a broader 2026 trend — doing more with the database you already run instead of bolting on new infrastructure. For the large majority of Node.js applications, a Postgres-backed queue delivers durable retries, scheduling, dead-letter handling, and transactional enqueue with a fraction of the operational overhead of Redis- or broker-based systems. You get one datastore to back up, one system to monitor, and the rare luxury of enqueuing a job in the same transaction as your business data.
Reach for a dedicated broker when you genuinely need extreme throughput, sub-millisecond latency, or polyglot publishers. Otherwise, start with pg-boss: it is the simplest thing that will almost certainly work, and it scales further than most teams expect. Begin with one queue, wire up retries and a dead-letter queue, add the dashboard, and grow from there.
Frequently Asked Questions
What is pg-boss in Node.js?
pg-boss is a Node.js library that turns a PostgreSQL database into a durable job queue. It uses Postgres SKIP LOCKED row locking to safely distribute jobs across many workers, and adds retries, scheduling, and dead-letter queues — all without a separate broker like Redis or RabbitMQ.
Is pg-boss production ready in 2026?
Yes. pg-boss is mature (version 12 as of 2026), actively maintained, and used in production by many teams. It offers transactional enqueue, exponential-backoff retries, cron scheduling, dead-letter queues, a web dashboard, and a CLI for schema migrations.
pg-boss vs BullMQ — which should I use?
Use pg-boss when you already run Postgres and want minimal infrastructure with transactional enqueue. Use BullMQ when you need very high throughput or advanced rate limiting and are comfortable operating a Redis cluster. For most apps under a few thousand jobs per second, pg-boss is the simpler choice.
How fast is pg-boss?
pg-boss comfortably handles thousands of jobs per second on a single node, which is enough for the vast majority of applications. Because workers poll the database, pickup latency is tens to hundreds of milliseconds rather than microseconds, so it is not suited to ultra-low-latency pipelines.
Does pg-boss support scheduled and recurring jobs?
Yes. pg-boss has a built-in scheduler that accepts standard cron expressions, persisted in Postgres so schedules survive restarts and run correctly across a cluster without duplicate firing. It also supports singleton and throttled jobs.
Do I need Redis to run a job queue in Node.js?
No. If your data already lives in Postgres, pg-boss lets you run a full job queue inside the same database — no Redis, no extra broker. This removes an entire piece of infrastructure to provision, secure, monitor, and back up.
Vivek Singh is the founder of Witarist and HireNodeJS.com — a platform connecting companies with pre-vetted Node.js developers. With years of experience scaling engineering teams, Vivek shares insights on hiring, tech talent, and building with Node.js.
Building background jobs? Hire a Node.js + Postgres expert.
HireNodeJS connects you with pre-vetted senior Node.js engineers who have shipped durable queues, retries, and Postgres-backed systems in production — available within 48 hours. No recruiter fees, no lengthy screening.
