Graphile Worker is a high-performance job queue for Node.js that uses PostgreSQL as its only dependency. No Redis, no RabbitMQ — jobs live in your existing database and are picked up using LISTEN/NOTIFY plus SKIP LOCKED for low-latency, contention-free dequeuing. If you already run Postgres and don't want another stateful service to operate, this is a compelling choice.
Why Postgres-only is attractive
Every additional broker is another thing to provision, secure, back up, and monitor. Graphile Worker folds the queue into the database you already have, so a job and the data it mutates can even share a transaction — enqueue a job atomically with the database change that triggered it, eliminating the classic "committed the row but lost the job" race.
| Property | Graphile Worker | Redis-based queues |
|---|---|---|
| Extra infra | none (uses Postgres) | a broker |
| Transactional enqueue | yes | no |
| Latency | very low (LISTEN/NOTIFY) | very low |
| Throughput ceiling | bounded by Postgres | very high |
The honest tradeoff: at extreme job volumes a dedicated broker scales further, but most applications never approach that ceiling.
Defining tasks
Tasks are plain async functions in a tasks/ directory, named by file:
// tasks/send_email.js
module.exports = async (payload, helpers) => {
const { email } = payload
helpers.logger.info(`sending to ${email}`)
// ... do the work
}Enqueue from your app, ideally inside the same transaction as the related write:
await addJob('send_email', { email: user.email })Deploying the worker
Graphile Worker runs as a standalone process or embedded in your app. For production, run it as its own service so background load is isolated from your web tier:
npx graphile-worker -c "$DATABASE_URL"First run installs its schema (graphile_worker) into the database. On PandaStack, attach a managed PostgreSQL to both your web service and the worker service; the injected DATABASE_URL is all Graphile Worker needs. The worker requires no public port.
Concurrency and connection budget
Control parallelism with --jobs (the number of jobs run concurrently) and size the pool accordingly. Because the worker holds Postgres connections, keep an eye on your database's connection limit — web app + worker concurrency + replicas all share it. On smaller tiers, set --jobs modestly and scale worker replicas only as needed.
npx graphile-worker -c "$DATABASE_URL" --jobs 5Scheduled jobs with crontab
Graphile Worker has a built-in crontab for recurring jobs — define them in a crontab file:
# m h dom mon dow task
0 3 * * * nightly_cleanup
*/15 * * * * refresh_cacheThis runs inside the worker process, so there's no separate scheduler to keep singleton. For very simple schedules you could instead use a platform cronjob to invoke a script, but Graphile Worker's crontab keeps everything in one place with backfill semantics.
Retries and failure
Failed jobs are retried with exponential backoff automatically. Tune maxAttempts per task. Jobs that exhaust retries remain in the table for inspection — query graphile_worker.jobs to see stuck or failing work. Because it's just SQL, your existing database tooling and dashboards work for monitoring the queue.
Go-live checklist
- Worker deployed as its own service from the same repo
- Managed Postgres attached;
DATABASE_URLshared --jobssized to workload and connection budget- Transactional
addJobwhere correctness matters - crontab file for recurring jobs
- Monitor
graphile_worker.jobsfor failures
References
- [Graphile Worker documentation](https://worker.graphile.org/)
- [Graphile Worker on GitHub](https://github.com/graphile/worker)
- [Graphile Worker cron](https://worker.graphile.org/docs/cron)
- [PostgreSQL SKIP LOCKED](https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE)
Graphile Worker is a perfect match for PandaStack's auto-wired databases: attach one managed PostgreSQL, run the worker as a second service, and you have a transactional job queue with zero extra infrastructure. Start free at [dashboard.pandastack.io](https://dashboard.pandastack.io).