Skip to Content

Multi-threading in NestJS or nodejs

Node.js is single-threaded by default, making CPU-intensive tasks a performance bottleneck. NestJS leverages worker threads to solve this—offloading heavy operations without blocking the main event loop.

You’ll quickly find that your app freezes, blocks requests, or slows down. That’s because Node.js is single-threaded by default.

So the challenge is:

How do we handle CPU-heavy workloads without blocking the main thread?

Let’s dive deep into how to implement true parallelism in Node.js using multi-threading techniques and learn which is best for your use case.

Why Worker Threads?

  • Problem: Synchronous CPU-heavy tasks (calculations, image processing) block the main thread.
  • Solution: Worker threads run parallel to the main thread, freeing the event loop.
  • Key Use Cases:
    • Mathematical computations (e.g., Fibonacci)
    • Image/video processing
    • Large dataset parsing

Setup & Dependencies

First, install Node.js (≥ v12) and create a NestJS project:

npm i -g @nestjs/clinest new worker-demo

Install sharp for image processing (our  example):

npm install sharp

Offload image resizing using sharp​.

Worker Setup

src/workers/image.worker.ts

import { parentPort, workerData } from 'worker_threads';
import sharp from 'sharp';

async function processImage(imagePath: string): Promise {
  return sharp(imagePath).resize(300, 300).jpeg().toBuffer();
}

processImage(workerData.path)
  .then((processed) => parentPort?.postMessage(processed))
  .catch((err) => parentPort?.postMessage({ error: err.message }));

Service & Controller

src/image/image.service.ts

import { Worker } from 'worker_threads';

@Injectable()
export class ImageService {
  async resizeImage(imagePath: string): Promise {
    return new Promise((resolve, reject) => {
      const worker = new Worker('./dist/workers/image.worker.js', {
        workerData: { path: imagePath },
      });

      worker.on('message', (message) => {
        if (message.error) reject(message.error);
        else resolve(message);
      });

      worker.on('error', reject);
      worker.on('exit', (code) => code !== 0 && reject());
    });
  }
}

src/image/image.controller.ts

import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';

@Controller('image')
export class ImageController {
  constructor(private readonly imageService: ImageService) {}

  @Post('resize')
  @UseInterceptors(FileInterceptor('file'))
  async resize(@UploadedFile() file: Express.Multer.File) {
    const resized = await this.imageService.resizeImage(file.path);
    return resized; // Returns binary image (set headers appropriately)
  }
}

Result: Uploaded images are resized in parallel threads, keeping your API responsive.

Node.js Execution Model: The Basics

  • Node.js uses a single-threaded event loop for asynchronous I/O (like reading files, querying DBs, etc.).
  • It’s fast for I/O-bound tasks, but CPU-bound operations can block the loop and freeze the app.

To fix that, Node.js provides:

  • worker_threads – true multi-threading
  • cluster – process-level parallelism
  • child_process – isolated subprocess execution

When to Use What in NestJS

Use Case Best Approach
CPU-intensive computation worker_threads
Scaling HTTP server cluster
Background processing or scripts child_process
Shared memory between threads worker_threads
Simple I/O tasks Stick to async/await

Hope you find it helpful!!



ARQ vs Celery — How to Pick the Right Python Task Queue