Skip to content

Jeelgor/distributed-job-queue-system

Repository files navigation

Distributed Job Queue System

A production-ready background job processing system built with Fastify, BullMQ, Prisma, and PostgreSQL.

Stack

Layer Technology
API Server Fastify
Queue BullMQ + Redis
Database PostgreSQL + Prisma
Language TypeScript
Logging Pino

Architecture

POST /api/jobs
      │
      ▼
 Prisma (PostgreSQL)        ← source of truth
      │
      ▼
 BullMQ Queue (Redis)       ← processing trigger
      │
      ▼
 BullMQ Worker
  ├── concurrency: 5
  ├── retries: 5 (exponential backoff)
  ├── on success → status: completed
  ├── on retry   → status: pending
  └── on failure → status: failed + move to DLQ

Prerequisites

  • Node.js 18+
  • PostgreSQL
  • Redis (Docker recommended)

Getting Started

1. Install dependencies

npm install

2. Start Redis

docker run -d --name redis -p 6379:6379 redis:alpine

3. Configure environment

Create a .env file in the project root:

DATABASE_URL=postgresql://user:password@localhost:5432/jobqueue
REDIS_HOST=localhost
REDIS_PORT=6379
CORS_ORIGIN=http://localhost:5173
NODE_ENV=development

4. Run database migrations

npx prisma migrate dev

5. Start the server

npm run dev

You should see:

BullMQ worker started  { queue: 'job-queue', concurrency: 5 }
Redis connected        { host: 'localhost', port: 6379 }
Server running at http://[::1]:3000

API Reference

Base URL: http://localhost:3000

Jobs

Method Endpoint Description
POST /api/jobs Create a new job
GET /api/jobs List jobs (filterable)
GET /api/jobs/stats Get status counts
GET /api/jobs/:id Get job by ID
POST /api/jobs/:id/retry Manually retry a failed job
PATCH /api/jobs/:id Update a job
DELETE /api/jobs/:id Delete a job
GET /health Health check

Create a Job

POST /api/jobs
Content-Type: application/json

{
  "type": "send-email",
  "payload": {
    "to": "user@example.com",
    "subject": "Welcome"
  }
}

Response 201:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "send-email",
  "payload": { "to": "user@example.com", "subject": "Welcome" },
  "status": "pending",
  "attempts": 0,
  "nextRunAt": null,
  "createdAt": "2026-05-04T10:00:00.000Z",
  "updatedAt": "2026-05-04T10:00:00.000Z"
}

List Jobs

GET /api/jobs?status=failed&type=send-email&limit=50&offset=0

Query parameters:

Param Type Default Description
status pending | processing | completed | failed Filter by status
type string Filter by job type
limit number 100 Max results (max 500)
offset number 0 Pagination offset

Get Stats

GET /api/jobs/stats

Response 200:

{
  "pending": 12,
  "processing": 3,
  "completed": 847,
  "failed": 5
}

Retry a Failed Job

POST /api/jobs/:id/retry

Resets status → pending, attempts → 0, re-enqueues in BullMQ.

Returns 400 if job is not in failed status.

Job Lifecycle

pending → processing → completed
                ↓
             failed (retry scheduled)
                ↓ (after max attempts)
             failed (permanent) → DLQ
Status Description
pending Waiting to be picked up by worker
processing Currently being executed
completed Finished successfully
failed Exhausted all retry attempts

Worker Behaviour

  • Concurrency: processes up to 5 jobs simultaneously
  • Retries: up to 5 attempts per job
  • Backoff: exponential — 2s, 4s, 8s, 16s, 32s between retries
  • Dead Letter Queue: permanently failed jobs are moved to job-dlq in Redis with full context (jobId, type, payload, reason, attempts, failedAt)

Project Structure

src/
├── server.ts              # entry point
├── app.ts                 # Fastify app setup
├── config/
│   ├── env.ts             # environment variables
│   ├── db.ts              # Prisma client
│   ├── redis.ts           # ioredis connection
│   ├── queue.ts           # BullMQ job queue
│   └── dlq.ts             # BullMQ dead letter queue
├── modules/
│   └── job/
│       ├── job.controller.ts
│       ├── job.service.ts
│       ├── job.route.ts
│       ├── job.schema.ts
│       └── job.types.ts
├── workers/
│   └── job.worker.ts      # BullMQ worker
├── plugins/
│   └── cors.ts            # CORS plugin
└── utils/
    └── logger.ts          # Pino logger

Scripts

Script Description
npm run dev Start with hot reload (ts-node-dev)
npm run build Compile TypeScript to dist/
npm start Run compiled production build

Frontend Integration

CORS is configured to allow http://localhost:5173 by default (Vite dev server).

Override via environment variable for production:

CORS_ORIGIN=https://yourdomain.com

Recommended polling intervals for a dashboard:

  • /api/jobs/stats — every 3–5 seconds
  • /api/jobs?status=processing — every 3–5 seconds
  • /api/jobs?status=failed — every 10–30 seconds

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors