Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Express.js Worker Threads

Worker Threads in Node.js allow you to run JavaScript in parallel threads, enabling you to take advantage of multi-core systems and perform CPU-intensive tasks without blocking the main thread. This guide covers key concepts, examples, and best practices for using Worker Threads in Express.js applications.

Key Concepts of Worker Threads

  • Worker: A class that represents an independent JavaScript execution thread.
  • parentPort: The communication channel between the main thread and the worker thread.
  • workerData: Data passed from the main thread to the worker thread when it is created.

Creating a Worker Thread

Use the Worker class to create a new worker thread that can run a separate JavaScript file:

Example: Creating a Worker Thread

// worker.js
const { parentPort, workerData } = require('worker_threads');

parentPort.on('message', (msg) => {
    console.log(`Worker received: ${msg}`);
    parentPort.postMessage(`Worker says: ${msg}`);
});

// main.js
const express = require('express');
const { Worker } = require('worker_threads');
const app = express();
const port = 3000;

app.get('/start-worker', (req, res) => {
    const worker = new Worker('./worker.js', { workerData: null });
    worker.postMessage('Hello, worker thread');
    worker.on('message', (msg) => {
        res.send(`Worker thread replied: ${msg}`);
    });
    worker.on('error', (err) => {
        res.status(500).send(`Worker error: ${err.message}`);
    });
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Using workerData

Pass data from the main thread to the worker thread using the workerData option:

Example: Using workerData

// worker.js
const { parentPort, workerData } = require('worker_threads');

parentPort.postMessage(`Received data: ${workerData}`);

// main.js
const express = require('express');
const { Worker } = require('worker_threads');
const app = express();
const port = 3000;

app.get('/start-worker', (req, res) => {
    const worker = new Worker('./worker.js', { workerData: 'Hello, worker thread' });
    worker.on('message', (msg) => {
        res.send(msg);
    });
    worker.on('error', (err) => {
        res.status(500).send(`Worker error: ${err.message}`);
    });
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Handling Worker Events

Handle various worker events such as online, exit, and error:

Example: Handling Worker Events

// main.js
const express = require('express');
const { Worker } = require('worker_threads');
const app = express();
const port = 3000;

app.get('/start-worker', (req, res) => {
    const worker = new Worker('./worker.js', { workerData: 'Hello, worker thread' });

    worker.on('online', () => {
        console.log('Worker is online');
    });

    worker.on('message', (msg) => {
        res.send(msg);
    });

    worker.on('error', (err) => {
        res.status(500).send(`Worker error: ${err.message}`);
    });

    worker.on('exit', (code) => {
        console.log(`Worker exited with code: ${code}`);
    });
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Communicating with Worker Threads

Use the postMessage method to send messages between the main thread and worker threads:

Example: Communicating with Worker Threads

// worker.js
const { parentPort } = require('worker_threads');

parentPort.on('message', (msg) => {
    console.log(`Worker received: ${msg}`);
    parentPort.postMessage(`Worker says: ${msg}`);
});

// main.js
const express = require('express');
const { Worker } = require('worker_threads');
const app = express();
const port = 3000;

app.get('/start-worker', (req, res) => {
    const worker = new Worker('./worker.js');
    worker.postMessage('Hello, worker thread');
    worker.on('message', (msg) => {
        res.send(`Worker thread replied: ${msg}`);
    });
    worker.on('error', (err) => {
        res.status(500).send(`Worker error: ${err.message}`);
    });
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Using Multiple Worker Threads

Use multiple worker threads to handle concurrent tasks efficiently:

Example: Using Multiple Worker Threads

// worker.js
const { parentPort, workerData } = require('worker_threads');

parentPort.postMessage(`Worker ${workerData.id} received data: ${workerData.msg}`);

// main.js
const express = require('express');
const { Worker } = require('worker_threads');
const app = express();
const port = 3000;

app.get('/start-workers', (req, res) => {
    const results = [];
    const createWorker = (id) => {
        return new Promise((resolve, reject) => {
            const worker = new Worker('./worker.js', { workerData: { id, msg: 'Hello, worker thread' } });
            worker.on('message', (msg) => {
                results.push(msg);
                resolve();
            });
            worker.on('error', (err) => {
                reject(err);
            });
        });
    };

    Promise.all([createWorker(1), createWorker(2), createWorker(3)])
        .then(() => {
            res.send(results);
        })
        .catch((err) => {
            res.status(500).send(`Worker error: ${err.message}`);
        });
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Best Practices for Using Worker Threads

  • Limit Resource Usage: Ensure worker threads do not consume excessive resources.
  • Handle Errors: Always handle errors in worker threads to prevent crashes.
  • Use Proper Communication: Use the postMessage method for proper communication between the main thread and worker threads.
  • Clean Up Threads: Ensure worker threads are properly cleaned up after completion.
  • Security: Ensure secure handling of data and commands in worker threads to prevent security risks.

Testing Worker Threads

Test your worker thread-based code using frameworks like Mocha, Chai, and Supertest:

Example: Testing Worker Threads

// Install Mocha, Chai, and Supertest
// npm install --save-dev mocha chai supertest

// test/worker-thread.test.js
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const express = require('express');
const { Worker } = require('worker_threads');

const app = express();

app.get('/start-worker', (req, res) => {
    const worker = new Worker('./worker.js', { workerData: 'Hello, worker thread' });
    worker.on('message', (msg) => {
        res.send(msg);
    });
    worker.on('error', (err) => {
        res.status(500).send(`Worker error: ${err.message}`);
    });
});

describe('GET /start-worker', () => {
    it('should start the worker thread and receive a reply', (done) => {
        request(app)
            .get('/start-worker')
            .expect(200)
            .end((err, res) => {
                if (err) return done(err);
                expect(res.text).to.include('Received data:');
                done();
            });
    });
});

// Define test script in package.json
// "scripts": {
//   "test": "mocha"
// }

// Run tests with NPM
// npm run test

Key Points

  • Worker: A class that represents an independent JavaScript execution thread.
  • parentPort: The communication channel between the main thread and the worker thread.
  • workerData: Data passed from the main thread to the worker thread when it is created.
  • Follow best practices for using Worker Threads, such as limiting resource usage, handling errors, using proper communication, cleaning up threads, and ensuring security.

Conclusion

Worker Threads in Node.js allow you to run JavaScript in parallel threads, enabling you to take advantage of multi-core systems and perform CPU-intensive tasks without blocking the main thread. By understanding and implementing the key concepts, examples, and best practices covered in this guide, you can effectively manage worker threads in your Express.js applications. Happy coding!