Reliable background jobs. Powerful scheduling. Built for developers.
A production-inspired distributed job scheduling platform with a real-time operator dashboard — Clean Architecture, BullMQ workers, PostgreSQL as the system of record, and Redis as the queue fabric.
TaskFlow is a control plane for background work. Instead of scattering setTimeout, ad-hoc cron, and one-off worker scripts across services, you define jobs once, schedule or enqueue them through an API, and let a worker pool execute them with retries, crash recovery, and a dead letter queue.
It is designed as a learning-and-portfolio system that mirrors how production schedulers are structured — not a toy todo app — while staying small enough to run on a laptop.
What you get out of the box
- Background jobs with named handlers (
noop,log,slow,fail) - Delayed enqueue (
delayMs/runAt) - Cron and interval scheduling via a visual Cron Builder
- Queue monitoring and worker heartbeats
- Retry policies with exponential or fixed backoff
- Dead letter queue for exhausted attempts
- Real-time dashboard (Socket.IO)
- JWT authentication and RBAC (
admin,operator,viewer)
┌──────────────┐
│ Client │
└──────┬───────┘
│
▼
┌──────────────────┐
│ React Dashboard │ Next.js · TanStack Query · Socket.IO client
│ (port 3000) │
└────────┬─────────┘
│ REST + WebSocket (JWT)
▼
┌──────────────────┐
│ REST API │ Express · Clean Architecture · Zod · Pino
│ (port 3001) │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ BullMQ │ Runs · Schedules · DLQ
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Redis │ Queue broker + locks
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Worker Pool │ Concurrency · Heartbeats · Stale recovery
└────────┬─────────┘
│
▼
┌──────────────────┐
│ PostgreSQL │ Jobs · Runs · Attempts · Logs · Users
└──────────────────┘
Design principles
- Domain and use cases never import Express,
pg, or BullMQ - Manual dependency injection in a single composition root
- PostgreSQL owns job/run truth; Redis/BullMQ carries
runIdpayloads - Optional embedded workers (
START_WORKERS=true) or a separatenpm run workerprocess
| Feature | Description |
|---|---|
| Delayed jobs | Enqueue with delayMs or runAt; status moves through delayed → queued → running |
| Cron scheduler | Visual Cron Builder (minutes / hourly / daily / weekly / monthly / custom) → BullMQ repeatables |
| Multiple workers | Configurable concurrency; separate worker process supported |
| Retry policies | Fixed or exponential backoff; domain-owned retry (BullMQ attempts = 1) |
| Dead letter queue | Failed runs after maxAttempts land on taskflow-dlq |
| Dashboard | Live counters, charts, worker cards, recent runs |
| Queue monitoring | Pending counts, queue position, scheduled countdown |
| Job history | Per-job execution table with filters and CSV export |
| Authentication | Register / login, JWT bearer tokens, role guards |
| Execution logs | Terminal-style viewer with search, copy, download |
| Worker health | Heartbeats every 10s; online/offline in the UI |
| Crash recovery | Stale running runs are recovered on worker start |
| Realtime updates | Socket.IO events: run.updated, run.log, worker.heartbeat |
| Layer | Choice |
|---|---|
| Runtime | Node.js 20+ |
| Framework | Express |
| Language | TypeScript (strict) |
| Validation | Zod |
| Logging | Pino |
| Auth | JWT + bcrypt |
| Realtime | Socket.IO |
| Architecture | Clean Architecture · Repository pattern · Manual DI |
| Layer | Choice |
|---|---|
| Framework | Next.js 15 (App Router) |
| UI | React 19 · Tailwind CSS · Lucide |
| Data | TanStack Query · Zustand |
| Charts | Recharts |
| Realtime | socket.io-client |
| Concern | Choice |
|---|---|
| System of record | PostgreSQL (pg) |
| Broker | Redis (ioredis) |
| Jobs | BullMQ |
| Tool | Purpose |
|---|---|
tsx |
Dev server / migrations |
| ESLint + Prettier | Lint & format |
node:test |
Domain unit tests |
Add screenshots under docs/ and they will render here:
| Dashboard | Jobs |
|---|---|
![]() |
![]() |
| Login | Job details |
|---|---|
![]() |
![]() |
Tip: capture the running UI at
http://localhost:3000and save PNGs to those paths.
- Node.js 20+
- PostgreSQL 14+
- Redis 7+
git clone https://github.com/<your-username>/Task-Flow.git
cd Task-Flowcd backend
cp .env.example .env
npm installcd frontend
cp .env.local.example .env.local
npm install# macOS (Homebrew)
brew install redis
brew services start redis
redis-cli ping # PONGNote: Homebrew Redis 8 may ship
loadmodulelines for modules that are not installed. If Redis exits immediately, comment out theloadmoduleentries in/opt/homebrew/etc/redis.confand restart the service.
# Create role + database (adjust for your local Postgres user)
psql -d postgres -c "CREATE ROLE taskflow LOGIN PASSWORD 'taskflow';" 2>/dev/null || true
psql -d postgres -c "CREATE DATABASE taskflow OWNER taskflow;" 2>/dev/null || true
psql -d taskflow -c "GRANT ALL ON SCHEMA public TO taskflow; ALTER SCHEMA public OWNER TO taskflow;"backend/.env
NODE_ENV=development
PORT=3001
HOST=0.0.0.0
LOG_LEVEL=info
DATABASE_URL=postgres://taskflow:taskflow@127.0.0.1:5432/taskflow
REDIS_URL=redis://127.0.0.1:6379
JWT_SECRET=dev-insecure-jwt-secret-change
JWT_EXPIRES_IN=7d
CORS_ORIGIN=http://localhost:3000
WORKER_CONCURRENCY=5
START_WORKERS=truefrontend/.env.local
NEXT_PUBLIC_API_URL=http://localhost:3001
NEXT_PUBLIC_WS_URL=http://localhost:3001cd backend
npm run migrate# Terminal 1 — API + embedded workers
cd backend && npm run dev
# Terminal 2 — Dashboard
cd frontend && npm run dev| Service | URL |
|---|---|
| Dashboard | http://localhost:3000 |
| API | http://localhost:3001 |
| Health | http://localhost:3001/health |
First user — open Register in the UI, or:
curl -s http://localhost:3001/api/v1/auth/register \
-H 'content-type: application/json' \
-d '{"email":"admin@taskflow.dev","password":"password123"}'The first registered account becomes admin.
Optional: separate worker process
cd backend
START_WORKERS=false npm run dev # API only
npm run worker # workers onlyTask-Flow/
├── backend/
│ ├── migrations/ # SQL migrations
│ ├── src/
│ │ ├── application/ # Use cases + ports
│ │ ├── config/ # Zod-validated env
│ │ ├── di/ # Manual composition root
│ │ ├── domain/ # Entities, errors, retry rules
│ │ ├── infrastructure/ # pg, BullMQ, JWT, handlers, Socket.IO
│ │ ├── presentation/http/ # Routes, middleware, controllers
│ │ ├── workers/ # BullMQ worker bootstrap
│ │ ├── scripts/migrate.ts
│ │ ├── app.ts
│ │ ├── server.ts
│ │ └── worker.ts
│ ├── package.json
│ └── tsconfig.json
├── frontend/
│ ├── src/
│ │ ├── app/ # Next.js App Router pages
│ │ ├── components/ # UI shell, cron builder, logs viewer
│ │ ├── lib/ # API client, formatters, cron helpers
│ │ └── stores/ # Zustand auth
│ └── package.json
├── docs/ # Specs + screenshot placeholders
└── README.md
Base URL: http://localhost:3001
Auth: Authorization: Bearer <jwt> (except health, register, login)
| Method | Path | Description |
|---|---|---|
GET |
/health |
Liveness |
GET |
/ready |
Postgres + Redis readiness |
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/auth/register |
Create user (first → admin) |
POST |
/api/v1/auth/login |
Returns { token, user } |
GET |
/api/v1/me |
Current principal |
| Method | Path | Roles | Description |
|---|---|---|---|
GET |
/api/v1/jobs |
any | List / search (q, status, limit, offset) |
POST |
/api/v1/jobs |
admin, operator | Create definition |
GET |
/api/v1/jobs/:jobId |
any | Job + schedule |
PATCH |
/api/v1/jobs/:jobId |
admin, operator | Update handler / retry |
POST |
/api/v1/jobs/:jobId/pause |
admin, operator | Pause |
POST |
/api/v1/jobs/:jobId/resume |
admin, operator | Resume |
PUT |
/api/v1/jobs/:jobId/schedule |
admin, operator | Set cron / interval |
DELETE |
/api/v1/jobs/:jobId/schedule |
admin, operator | Remove schedule |
POST |
/api/v1/jobs/:jobId/runs |
admin, operator | Enqueue (Idempotency-Key optional) |
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/runs |
List / filter runs |
GET |
/api/v1/runs/:runId |
Run + attempts + logs |
POST |
/api/v1/runs/:runId/cancel |
Cancel pending run |
POST |
/api/v1/runs/:runId/retry |
Retry failed / DLQ run |
GET |
/api/v1/analytics |
Status counts + daily series |
GET |
/api/v1/workers |
Worker heartbeats |
| Status | Meaning |
|---|---|
| Queued | Waiting in BullMQ for a worker |
| Delayed / Scheduled | Waiting for delay or next cron tick |
| Running | Worker has leased the run and is executing a handler |
| Succeeded | Handler completed; attempt recorded |
| Failed | Attempt failed; will retry if attempts remain |
| Dead lettered | maxAttempts exhausted; message also written to DLQ |
| Cancelled | Cooperative cancel before / instead of execution |
Enqueue / Cron tick
│
▼
┌─────────┐ delay/cron ┌───────────┐
│ Queued │◄────────────────────│ Delayed │
└────┬────┘ └───────────┘
│ worker lease
▼
┌─────────┐
│ Running │
└────┬────┘
│
┌────┴────┬──────────────┐
▼ ▼ ▼
Succeeded Failed ──retry──► Queued/Delayed
│
└─ exhausted ──► Dead lettered
- Worker process starts (embedded in API or via
npm run worker). - Registers a heartbeat row and begins pulsing every 10s.
- Runs stale recovery for
runningjobs past their timeout. - Consumes
taskflow-runswith concurrency =WORKER_CONCURRENCY(default 5). - For each message: load run → mark running → create attempt → execute handler → succeed or apply retry/DLQ.
- Schedule consumer turns cron ticks into new runs.
- DLQ consumer logs terminal failures for operators.
Handlers today are in-process (noop, log, slow, fail). Swap implementations under infrastructure/handlers without changing use cases.
| Widget | What it shows |
|---|---|
| Welcome header | Date, worker health indicator, API uptime |
| Quick actions | Create job · Enqueue · View runs |
| Stat cards | Queued, running, scheduled, succeeded, failed, active workers |
| Queue activity | Success vs failed over the last 7 days |
| Jobs per minute | Throughput from recent runs |
| Worker utilization | Running slots vs idle capacity |
| Worker cards | Name, status, concurrency, last heartbeat |
| Recent runs | Status, trigger type, duration, retries |
Job detail pages add next/last run, countdown when scheduled, execution timeline, terminal logs, and history with export.
- Rate limiting on public API routes
- Priority queues
- Horizontally scaled worker fleets with placement affinity
- Prometheus / OpenTelemetry metrics
- Outbound webhooks on run terminal states
- Email notifications for DLQ and schedule failures
- Audit log of mutating operator actions
- Docker Compose for Postgres + Redis + API + UI
- OpenAPI / generated client SDK
- Fork the repo and create a feature branch.
- Keep Clean Architecture boundaries (no framework imports in
domain/). - Run backend checks before opening a PR:
cd backend && npm test && npm run lint && npx tsc --noEmit
cd ../frontend && npx tsc --noEmit- Prefer small, focused PRs with a clear description of behavior changes.
MIT — see LICENSE when present, or treat this repository as MIT-licensed for portfolio use.
Yug Chaudhary
Built as a production-style systems project: distributed scheduling, queue semantics, and an operator UX you would expect from modern SaaS platforms.
If this README helped you run TaskFlow, a star on the repo is appreciated.



