A distributed background task processing system built with TypeScript and Redis.
Let's say a user signs up on your website and you want to send them a welcome email. If you send the email inside the API call, the user waits until the email is actually sent. That's slow.
Instead, you push a send_email task to a Redis queue, respond to the user instantly, and a background worker picks it up and sends the email separately.
That's what this system does. It has two services:
- Producer — an Express server with a
POST /enqueueendpoint. You send it a task, it validates it, assigns a unique ID, and pushes it to Redis. - Worker — pulls tasks from Redis, processes them, handles retries if something fails, and logs everything to a file.
It's not limited to emails — you can add any task type (image resizing, PDF generation, etc.) by just adding a case in the processor switch statement.
Client (POST /enqueue) → Producer → Redis (task_queue) → Worker → Execute Task
↓
On failure:
retries > 0 → re-enqueue
retries = 0 → dead letter queue
The worker runs multiple concurrent loops using Promise.all (configured via WORKER_CONCURRENCY). Each loop does a BLPOP on the Redis list which blocks until a task is available — so no polling or busy waiting.
- TypeScript + Node.js
- Express.js for HTTP
- ioredis for Redis
- dotenv for env config
- fs module for logging
Just need Docker installed. No Node.js, no Redis setup, nothing else.
cd conquer-ts
docker compose up --buildThis starts Redis, Producer (port 3000), and Worker (port 3001) automatically.
To stop everything:
docker compose downYou need Node.js and a running Redis instance. I used Docker for Redis:
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latestPort 8001 gives you RedisInsight in the browser which is helpful for seeing what's in the queue.
cd conquer-ts
npm installCopy .env.example to .env and fill in your values:
REDIS_URL=redis://localhost:6379/1
PORT_PRODUCER=3000
PORT_WORKER=3001
WORKER_CONCURRENCY=3
Open two terminals:
# Terminal 1 - start the producer
npm run producer
# Terminal 2 - start the worker
npm run workerSend a task:
curl -X POST http://localhost:3000/enqueue \
-H "Content-Type: application/json" \
-d '{"type":"send_email","retries":3,"payload":{"to":"test@test.com","subject":"hello"}}'You should see the worker pick it up and log it. Check the metrics:
curl http://localhost:3001/metricsHealth checks:
curl http://localhost:3000/health
curl http://localhost:3001/healthTry sending an invalid task to see validation:
curl -X POST http://localhost:3000/enqueue \
-H "Content-Type: application/json" \
-d '{"type":"","retries":3,"payload":{}}'send_email— needstoandsubjectin payloadresize_image— needsnew_xandnew_yin payloadgenerate_pdf— just logs that it's generating
To add a new type, add a case in src/internal/processor.ts. That's it.
conquer-ts/
├── src/
│ ├── config/
│ │ └── redis.ts # Redis connection
│ ├── internal/
│ │ ├── task.ts # Task and Metrics interfaces
│ │ ├── processor.ts # switch-case task execution
│ │ └── logger.ts # logs to file
│ ├── producer/
│ │ └── index.ts # POST /enqueue, GET /health
│ └── worker/
│ └── index.ts # task processing loop, GET /metrics, GET /health
├── logs/
│ └── worker.log
├── Dockerfile.producer
├── Dockerfile.worker
├── docker-compose.yml
├── .dockerignore
├── .env.example
├── package.json
└── tsconfig.json
- Dead Letter Queue — failed tasks (after all retries) go to
task_queue:deadinstead of disappearing - Graceful Shutdown — worker stops cleanly on Ctrl+C, finishes current tasks before exiting
- Health checks — both services have
/healththat pings Redis - Input validation — producer validates each field with clear error messages
- Task IDs — every task gets a UUID, makes it easy to trace in logs
- Configurable concurrency — set
WORKER_CONCURRENCYin .env instead of hardcoding
The worker writes to logs/worker.log. Looks like this:
SUCCESS | Time: 2026-03-18T20:58:46.588Z | Task ID: 254f3cea-... | Type: send_email | Payload: {"to":"test@test.com","subject":"hello"} | retries left: 3
FAILURE | Time: 2026-03-18T21:03:14.664Z | Task ID: 8dbcfafd-... | ERROR: UnSupported Task:unknown_task | Type: unknown_task | Payload: {"data":"test"} | retries left: 2

