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!