A production-ready demonstration of handling CPU-intensive operations (Excel file processing) in Node.js without blocking the main thread, showcasing scalable architecture patterns for high-concurrency applications.
This project demonstrates:
- ✅ Non-blocking I/O: Processing large files without blocking the main thread
- ✅ Worker Threads: CPU-intensive work runs in separate threads
- ✅ Job Queues: BullMQ for reliable task distribution and retry logic
- ✅ Horizontal Scalability: Architecture supports multiple workers across servers
- ✅ Progress Tracking: Real-time progress updates persisted in PostgreSQL
- ✅ Concurrency Control: Handle multiple simultaneous file uploads
- ✅ Production Patterns: Error handling, retries, and monitoring
┌─────────────┐
│ Client │
└──────┬──────┘
│ Upload Excel
▼
┌─────────────────────────────────────────────┐
│ NestJS API (Main Thread) │
│ ┌──────────────────────────────────────┐ │
│ │ 1. Save file to disk │ │
│ │ 2. Create DB record (QUEUED) │ │
│ │ 3. Add job to Redis queue │ │
│ │ 4. Return jobId immediately │ │
│ └──────────────────────────────────────┘ │
└──────────────────┬──────────────────────────┘
│
▼
┌──────────────────┐
│ Redis Queue │
│ (BullMQ) │
└────────┬─────────┘
│
▼
┌────────────────────────────────┐
│ Queue Processor Worker │
│ ┌──────────────────────────┐ │
│ │ 1. Pick job from queue │ │
│ │ 2. Update DB (PROCESSING)│ │
│ │ 3. Spawn Worker Thread │ │
│ │ 4. Parse Excel file │ │
│ │ 5. Update progress │ │
│ │ 6. Save results to DB │ │
│ └──────────────────────────┘ │
└────────────────────────────────┘
│
▼
┌─────────────────┐
│ PostgreSQL │
│ (Progress DB) │
└─────────────────┘
Excel parsing runs in separate threads, preventing the main event loop from blocking:
const worker = new Worker('./excel.worker.js', {
workerData: { buffer: Array.from(buffer) }
});- Jobs persist in Redis (survives crashes)
- Automatic retry with exponential backoff
- Configurable concurrency
- Job status tracking
Real-time progress updates stored in PostgreSQL:
QUEUED→PROCESSING→COMPLETED/FAILED- Progress percentage (0-100%)
- Processed row count
- Error messages on failure
- Vertical: Add more worker threads per server
- Horizontal: Add more servers, all connecting to same Redis queue
- Load balancing: Multiple workers process jobs concurrently
- Runtime: Node.js with TypeScript
- Framework: NestJS
- Queue: BullMQ (Redis-based)
- Database: PostgreSQL with Prisma ORM
- Excel Processing: ExcelJS
- Containerization: Docker & Docker Compose
- Node.js 18+
- Docker & Docker Compose
- npm or yarn
git clone https://github.com/yourusername/cpu-intensive-handling.git
cd cpu-intensive-handlingnpm installcp .env.example .envEdit .env if needed:
# Database
DATABASE_URL="postgresql://root:root@localhost:5432/cpu_intensive_test?schema=public"
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
# Server
PORT=3000
NODE_ENV=developmentdocker-compose up -dThis starts:
- PostgreSQL on port 5432
- Redis on port 6379
npx prisma migrate dev# Development mode with hot reload
npm run start:dev
# Production mode
npm run build
npm run start:prodPOST /excel/upload
Content-Type: multipart/form-data
# Response
{
"message": "File queued for processing",
"jobId": "1",
"fileId": "uuid-here",
"status": "QUEUED"
}Test with the included load test script:
# Basic test (10 concurrent, 50 total)
./test_excel_process.sh
# Heavy load (20 concurrent, 100 total)
CONCURRENT=50 TOTAL=500 ./loadtest.sh- ✅ API responds immediately with jobId
- ✅ Multiple uploads handled concurrently
- ✅ No request blocking
- ✅ Progress tracked in real-time
- Understanding Node.js limitations: Single-threaded event loop
- Worker Threads: Moving CPU work off main thread
- Job Queues: Async task processing patterns
- Concurrency: Handling multiple operations simultaneously
- Data persistence: Tracking state across requests
- Error handling: Retries, failure states, cleanup
- Horizontal scaling: Queue-based architecture
- Separation of concerns: API layer vs processing layer
- Database design: Status tracking, job metadata
- Infrastructure: Docker, Redis, PostgreSQL
- Container orchestration: Docker Compose
- Configuration management: Environment variables
- Resource management: Redis memory policies
- Monitoring: Job states, progress tracking
- ✅ Environment-based configuration
- ✅ Error handling and retries
- ✅ Job progress tracking
- ✅ Database transaction safety
- ✅ File cleanup after processing
- ✅ Redis eviction policy (noeviction)
src/
├── modules/
│ ├── excel/
│ │ ├── excel.controller.ts # API endpoints
│ │ ├── excel.service.ts # Business logic
│ │ ├── excel.processor.ts # Queue job processor
│ │ ├── excel.worker.ts # Worker thread code
│ │ └── excel.module.ts # Module configuration
│ └── prisma/
│ └── prisma.service.ts # Database service
├── app.module.ts # Main application module
└── main.ts # Application entry point
prisma/
├── schema.prisma # Database schema
└── migrations/ # Database migrations
uploads/ # Temporary file storage (gitignored)
enum ExcelStatus {
QUEUED
PROCESSING
COMPLETED
FAILED
}
model ProcessedExcel {
id String @id @default(uuid())
name String // Original filename
file String // File path
status ExcelStatus @default(QUEUED)
jobId String? @unique
progress Int @default(0)
totalRows Int?
processedAt DateTime?
failedReason String?
fileSize Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}This is a portfolio/demonstration project, but suggestions are welcome!
MIT
Marlon Anthony Laguna Jurado
- GitHub: @AnthonyLJ96
- LinkedIn: marlonlj
If you want to learn more about these concepts:
⭐ If this project helped you understand concurrent processing in Node.js, please give it a star!