A distributed job scheduling platform with a React dashboard, Express API, Redis-backed queueing, a dedicated scheduler, and worker processes for execution and observability.
- Overview
- Architecture
- Feature Breakdown
- Technology Stack
- Folder Structure
- System Design
- Database Schema
- Job Workflow
- API Overview
- Installation
- Environment Variables
- Local Development Setup
- Deployment Guide
- Monitoring & Observability
- Testing
- Performance & Benchmarks
- Roadmap
- Screenshots
- License
- Author / Contact Information
This project is a full-stack distributed job scheduler that lets users create, inspect, pause, resume, and delete jobs while a separate scheduler and worker pair handle execution.
The system is designed around service separation, with the dashboard, API, scheduler, and worker running independently while sharing Redis, database, queue, and observability packages.
Core capabilities:
- Job creation for
ONCE,DELAYED, andCRONexecution modes - Job history, recent execution, and failed-job visibility
- Redis-backed queue depth tracking and worker presence tracking
- Prometheus metrics exposure from all runtime services
- React dashboard for operational management
flowchart TB
subgraph Client["Dashboard (React + TypeScript)"]
Dashboard["Dashboard UI"]
ApiClient["API Client"]
end
subgraph API["API Service (Express + TypeScript)"]
ApiApp["Express App"]
JobRoutes["Job Routes"]
JobController["Job Controller"]
JobService["Job Service"]
Audit["Audit Service"]
ApiMetrics["/metrics"]
end
subgraph Scheduler["Scheduler Service"]
SchedulerApp["Scheduler"]
SchedulerCore["Scheduler Core"]
SchedulerMetrics["/metrics"]
end
subgraph Worker["Worker Service"]
WorkerApp["Worker"]
JobWorker["Job Worker"]
QueueEvents["Queue Events"]
WorkerMetrics["/metrics"]
end
subgraph Packages["Shared Packages"]
Shared["Shared Helpers"]
Database["Prisma Database Package"]
Observability["Observability Package"]
end
subgraph Queue["Queue Layer"]
BullMQ["BullMQ Queue"]
Redis[("Redis")]
end
subgraph Storage["Database"]
Postgres[("PostgreSQL")]
end
subgraph Monitoring["Monitoring"]
Prometheus["Prometheus"]
Grafana["Grafana"]
end
Dashboard --> ApiClient
ApiClient --> ApiApp
ApiApp --> JobRoutes
JobRoutes --> JobController
JobController --> JobService
JobController --> Audit
JobService --> BullMQ
JobService --> Database
SchedulerApp --> SchedulerCore
SchedulerCore --> BullMQ
SchedulerCore --> Database
WorkerApp --> JobWorker
WorkerApp --> QueueEvents
JobWorker --> BullMQ
JobWorker --> Database
BullMQ --> Redis
Database --> Postgres
ApiApp --> Observability
SchedulerApp --> Observability
WorkerApp --> Observability
Observability --> ApiMetrics
Observability --> SchedulerMetrics
Observability --> WorkerMetrics
ApiMetrics --> Prometheus
SchedulerMetrics --> Prometheus
WorkerMetrics --> Prometheus
Prometheus --> Grafana
| Layer | Provider | Notes |
|---|---|---|
| Frontend | Vite + React | Dashboard UI for job management |
| API | Express | Validates requests, persists jobs, exposes health and metrics |
| Scheduler | Node.js service | Maintains scheduling loop and heartbeat in Redis |
| Worker | Node.js service | Consumes jobs and tracks worker heartbeat |
| Database | PostgreSQL + Prisma | Stores jobs, runs, failures, and audit logs |
| Queue / Cache | Redis + BullMQ | Queueing and service coordination |
| Monitoring | Prometheus + Grafana | Metrics scraping and visualization |
- Create jobs with immediate, delayed, or cron-based execution
- View all jobs and individual job details
- Pause, resume, and delete jobs
- Inspect job run history, recent executions, and recent failures
- Scheduler heartbeat written to Redis every 5 seconds
- Worker heartbeat written to Redis every 5 seconds
- Worker registration tracked through a Redis
workersset - Queue depth reported by the API for dashboard visibility
- Prometheus metrics endpoint on each runtime service
- Queue depth gauge
- Job creation, completion, failure, and throughput counters
- Histogram support for job duration tracking
Backend
| Category | Technology |
|---|---|
| Runtime | Node.js |
| Framework | Express |
| Language | TypeScript |
| Database | PostgreSQL |
| ORM | Prisma |
| Queue / Cache | Redis, BullMQ, ioredis |
| Validation | Zod |
| Metrics | prom-client |
| Logging | Pino |
Frontend
| Category | Technology |
|---|---|
| Framework | React + TypeScript |
| Build Tool | Vite |
| Data Fetching | Axios, TanStack Query |
| Routing | React Router |
| Styling | Tailwind CSS |
| Icons | Lucide React |
| UX | React Hot Toast |
distributed-job-scheduler/
├── apps/
│ ├── api/
│ │ ├── src/
│ │ │ ├── app.ts
│ │ │ ├── controllers/
│ │ │ ├── middlewares/
│ │ │ ├── routes/
│ │ │ ├── services/
│ │ │ └── validators/
│ │ └── package.json
│ ├── dashboard/
│ │ ├── src/
│ │ │ ├── api/
│ │ │ ├── components/
│ │ │ ├── hooks/
│ │ │ ├── layouts/
│ │ │ ├── pages/
│ │ │ └── utils/
│ │ └── package.json
│ ├── scheduler/
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── scheduler.ts
│ │ │ ├── cleanup.ts
│ │ │ └── services/
│ │ └── package.json
│ └── worker/
│ ├── src/
│ │ ├── index.ts
│ │ └── workers/
│ └── package.json
├── benchmarks/
├── docker/
├── packages/
│ ├── database/
│ ├── observability/
│ └── shared/
├── docker-compose.yml
├── package.json
└── README.md
The codebase follows a layered service architecture:
| Layer | Files | Responsibility |
|---|---|---|
| Routes | apps/api/src/routes/* |
Endpoint definitions and request wiring |
| Controllers | apps/api/src/controllers/* |
Request handling and response shaping |
| Services | apps/api/src/services/* |
Job logic, audit logging, and query orchestration |
| Validation | apps/api/src/validators/* |
Input validation with Zod |
| Shared Infra | packages/shared/* |
Redis connection and queue helpers |
| Database | packages/database/* |
Prisma client and schema access |
| Observability | packages/observability/* |
Metrics registry and logger setup |
| Scheduler | apps/scheduler/src/* |
Scheduling loop and cleanup coordination |
| Worker | apps/worker/src/* |
Job execution and queue event processing |
This separation keeps request handling, persistence, queue coordination, and observability independently maintainable.
erDiagram
USER {
String id
String email
String name
DateTime createdAt
DateTime updatedAt
}
JOB {
String id
String name
String description
Json payload
String type
String cronExpression
Boolean active
Int priority
DateTime nextRunAt
Int maxRetries
Int timeoutMs
String status
DateTime createdAt
DateTime updatedAt
}
JOB_RUN {
String id
String jobId
String status
DateTime startedAt
DateTime finishedAt
String error
Int duration
String workerId
Int attempts
}
FAILED_JOB {
String id
String jobId
Json payload
String reason
Int attempts
DateTime failedAt
}
AUDIT_LOG {
String id
String entityType
String entityId
String action
Json metadata
DateTime createdAt
}
JOB ||--o{ JOB_RUN : has
JOB ||--o{ FAILED_JOB : has
JOB ||--o{ AUDIT_LOG : logs
| Model | Purpose |
|---|---|
User |
Auth-related user record |
Job |
Schedulable job definition |
JobRun |
Execution history for a job |
FailedJob |
Failed job record with reason and attempts |
AuditLog |
Event audit trail for job actions |
sequenceDiagram
participant User
participant Dashboard
participant API
participant Postgres as PostgreSQL
participant Redis
participant Scheduler
participant Worker
User->>Dashboard: Create or manage a job
Dashboard->>API: POST /jobs/create or job action request
API->>API: Validate input with Zod
API->>Postgres: Persist job via Prisma
API->>Redis: Read queue depth / worker state when needed
Scheduler->>Redis: Refresh scheduler heartbeat
Scheduler->>Worker: Coordinate scheduling through shared queue state
Worker->>Postgres: Record job run / failure data
Worker->>Redis: Refresh worker heartbeat
API-->>Dashboard: Return updated job state
- The dashboard sends a job request to the API.
- The API validates the payload and persists the job with Prisma.
- The scheduler keeps scheduling state alive and updates a Redis heartbeat.
- The worker processes jobs, records results, and updates its own heartbeat.
- The API exposes job state, queue depth, and recent execution data back to the dashboard.
Base URL:
http://localhost:5000
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Checks Redis, database, scheduler heartbeat, and API status |
GET |
/metrics |
Prometheus-formatted metrics |
GET |
/jobs/dashboard |
Queue depth for the dashboard |
GET |
/api/worker |
Registered worker IDs from Redis |
| Method | Endpoint | Description |
|---|---|---|
POST |
/jobs/create |
Create a job |
GET |
/jobs/get |
List jobs |
GET |
/jobs/failed |
List failed jobs |
GET |
/jobs/:id |
Get a single job |
DELETE |
/jobs/:id/delete |
Delete a job |
PATCH |
/jobs/:id/pause |
Pause a job |
PATCH |
/jobs/:id/resume |
Resume a job |
GET |
/jobs/:id/history |
Get execution history for a job |
GET |
/jobs/job-runs/recent |
Recent executions |
GET |
/jobs/job-fails/recent |
Recent failed jobs |
The create endpoint accepts the following fields:
{
"name": "Daily Report",
"description": "Generate and send a report",
"payload": { "reportId": "rpt_123" },
"type": "ONCE",
"cronExpression": "0 9 * * *",
"active": true,
"priority": 1,
"nextRunAt": "2026-07-28T09:00:00.000Z",
"maxRetries": 3,
"timeoutMs": 30000
}Accepted job types:
ONCEDELAYEDCRON
- Node.js 18+
- npm
- PostgreSQL
- Redis
From the repository root:
npm installCreate a root .env file:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/distributed_job_scheduler
REDIS_URL=redis://localhost:6379
CORS_ORIGIN=http://localhost:5173
VITE_API_BASE_URL=http://localhost:5000
LOG_LEVEL=info
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=distributed_job_schedulerDATABASE_URLis used by Prisma and the API layer.REDIS_URLis used by the shared Redis connection and the runtime services.CORS_ORIGINcontrols which frontend origin the API accepts.VITE_API_BASE_URLis used by the dashboard Axios client.LOG_LEVELis used by the logger configuration.POSTGRES_*values are used bydocker-compose.yml.
# Terminal 1 — API
npm run dev --workspace=@scheduler/api
# Terminal 2 — Scheduler
npm run dev --workspace=scheduler
# Terminal 3 — Worker
npm run dev --workspace=worker
# Terminal 4 — Dashboard
npm run dev --workspace=dashboardnpm run devThat runs the dev script across all workspaces.
npm run build
npm startdocker compose up --build| Layer | Provider | Notes |
|---|---|---|
| Frontend | Vercel / Netlify | Static dashboard build |
| API | Render / Railway / Docker host | Express API and health endpoints |
| Scheduler | Render / Railway / Docker host | Long-running scheduler process |
| Worker | Render / Railway / Docker host | Long-running worker process |
| Database | PostgreSQL managed service | Prisma-backed persistence |
| Queue / Cache | Redis managed service | Shared queue and heartbeat store |
The project exposes Prometheus-compatible metrics from the API, scheduler, and worker services.
Tracked signals include:
- Total jobs created
- Total jobs completed
- Total jobs failed
- Queue depth
- Worker throughput
- Job execution duration
- API health route checks Redis, database, and scheduler heartbeat state.
- Scheduler writes a
scheduler:heartbeatkey to Redis. - Worker writes per-worker heartbeat keys to Redis.
- The dashboard can surface queue depth through the API.
- API and runtime services use structured logging.
LOG_LEVELcontrols logger verbosity where supported.
Suggested validation flows:
- Create a job and verify it appears in
/jobs/get - Pause and resume a job and confirm state changes
- Check
/healthwith Redis or PostgreSQL temporarily offline - Verify
/metricsexports Prometheus text format
- A benchmark script exists at benchmarks/create-10000-jobs.ts.
- It uses
autocannonto stressPOST /jobs/create. - No benchmark results are committed yet.
- Add a committed
.env.examplefor faster onboarding - Add end-to-end tests for job creation and lifecycle actions
- Add dashboard screenshots and a short demo GIF
- Reconcile any future dashboard calls with API routes as features expand
- Add a deployment section with real live URLs once available
This project is currently listed as ISC in the workspace package manifests.





