pg-boss for Durable Background Jobs

The customer problem was not “we need a queue”. The problem was that a slow operation made the user wait with no clear answer.

That distinction matters. A queue is an implementation detail. The product problem is uncertainty. The user clicks a button, the app thinks for too long, and nobody knows whether the work started, failed, or will finish later.

For that kind of workflow, I like moving slow work out of the request and into a durable background job. A common example is a report export or CSV import. The web request should validate the basics, create a job, and send the user to a status page. The worker can do the slow part.

await boss.send("report.export", {
  reportId,
  requestedBy: userId,
  idempotencyKey,
});

This is a fake job name and payload, but the shape is realistic. The request captures enough information for the worker to do the work later. The user gets a job they can track.

I like pg-boss for this stage because it uses PostgreSQL. If the app already depends on Postgres for durable state, a database-backed queue can be a practical choice. It avoids adding a separate queue service before the product needs that extra moving part.

The important UI is the status page. It should show states the user understands:

queued
running
done
failed

This sounds small, but it changes the experience. The user does not have to guess whether the button worked. Support does not have to guess either. The system has a record of the job and the latest state.

Retries are useful, but only if the job can handle them. A retry should not create duplicate records, send duplicate emails, or charge twice. That is why an idempotency key matters. The worker needs a way to say, “I have already handled this logical request.”

Failure also needs a product answer. A failed job should not disappear into logs only engineers can read. The user may need a short error message, a retry button, or a way to download validation errors. The exact UI depends on the workflow, but the failure state should be designed.

The trade-off is that a queue does not remove complexity. It moves complexity into a different place. Now there is a worker process, job monitoring, retry behavior, and a question of what happens during deploys. That is still simpler than making a user wait for long work inside one request, but it is not free.

I would use pg-boss when:

  • the app already uses PostgreSQL
  • job volume is moderate
  • operational simplicity matters
  • the team wants durable jobs without a separate queue service

I would consider a separate queue when job volume becomes a main bottleneck, workers need more specialized scaling, or the system already has queue infrastructure.

The lesson for me is that background jobs are a product feature, not just backend plumbing. The queue matters, but the visible workflow matters more. A durable job without a clear status page still leaves the user guessing.

Related Posts

Astro for Documentation and a Professional Site

I use Astro because this site is mostly writing. I do not need a heavy app framework for pages that should load fast and be easy to edit. That sounds simple, but it is the mai

read more

Localization in Product Apps

Localization is not only replacing English strings with another language. In a product app, language touches workflow. It changes labels, validation messages, dates, empty states, permissions copy, d

read more

MCP as a Safe AI Integration Boundary

MCP is interesting because it makes AI integrations feel less like prompt magic and more like software boundaries. That is the part I care about. A model should no

read more

Zod, OpenAPI, and Swagger for API Contracts

A public API is not just backend code. It is a product surface for another developer. That means the contract has to be readable. It also has to be enforced at runtime. Types in the app are useful, b

read more

Pragmatic Drag and Drop for Real Ordering Tasks

Drag and drop is easy to add for a demo and harder to make reliable for real work. The product question is not "can the item move on screen?" The question is whether the user can safely change an ord

read more

Prisma and PostgreSQL as the Product Source of Truth

I do not think of PostgreSQL as only infrastructure. In a product app, it is where the product remembers what happened. That makes database design a product decision. I

read more

React Router for Full-Stack Product Workflows

A route is not only a URL. In a product app, a route often represents a task the user is trying to finish. That sounds obvious, but it changes how I design the code. A settings page that starts an im

read more

shadcn-Style UI as an Owned Product System

I like copied UI primitives because they make the component library feel like part of the app, not something the app is borrowing. That is the part of the shadcn/ui-style ap

read more

Dense Operational UI with Tables and Editors

Sometimes a simple form is the wrong UI. If the user needs to compare many values and make careful edits, a table can be kinder than a long page of inputs. Dense UI has a bad reputation when it is us

read more

Vercel AI SDK with Explicit Tool Boundaries

The risky part of an AI feature is not the chat UI. The risky part is what the chat is allowed to do. It is easy to make an assistant feel powerful by giving it tools. With something like the [Vercel

read more

Vertical Slice Architecture with Dependency-Cruiser

I like vertical slices because they make a feature easier to delete, move, or review. The folder structure is not the main value. The value is that the code for one workflow is not spread across ten u

read more

Testing Product Workflows with Vitest and Playwright

I do not want a test suite that only proves functions work. I want it to protect the workflows that would hurt if they broke. That does not mean every rule needs a browser test. Browser tests are val

read more

Zod Beyond Validation

Zod is usually introduced as a validation library. That is true, but the more useful idea is boundary definition. A TypeScript type only helps after data is already inside the pro

read more