Worker threads
A thread is a separate line of work inside one program. Your Node.js code runs in one thread, and a long computation blocks the event loop: the server stops answering. The node:worker_threads module runs code in an additional thread — a worker. It computes in parallel while the main thread keeps serving requests.
A worker in one file
A file can be both the main program and the worker at once — the isMainThread flag tells them apart:
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
function countWords(text) {
return text.split(/\s+/).filter(Boolean).length;
}
if (isMainThread) {
const worker = new Worker(new URL(import.meta.url), {
workerData: 'Hello from the blog main thread',
});
worker.on('message', (count) => console.log(`Words: ${count}`));
} else {
parentPort.postMessage(countWords(workerData));
}It prints "Words: 6". The main thread creates a worker from the same file (import.meta.url is its address) and passes data in workerData. The worker computes and sends the result with parentPort.postMessage().
How they communicate
Threads share no variables. They exchange messages, and the data is copied:
| Where | What to use |
|---|---|
| Main thread → worker at creation | workerData |
| Worker → main thread | parentPort.postMessage(data) |
| Main thread receives | worker.on('message', fn) |
| An error in the worker | worker.on('error', fn) |
A separate worker file
In a project, a worker usually has its own file. Create src/workers/stats.js — it computes statistics over all posts:
// src/workers/stats.js
import { parentPort, workerData } from 'node:worker_threads';
const words = workerData.posts
.map((post) => post.body.split(/\s+/).filter(Boolean).length)
.reduce((sum, n) => sum + n, 0);
parentPort.postMessage({ posts: workerData.posts.length, words });The main thread wraps the start in a promise to await the result:
// src/stats.js
import { Worker } from 'node:worker_threads';
export function calculateStats(posts) {
return new Promise((resolve, reject) => {
const worker = new Worker(new URL('./workers/stats.js', import.meta.url), {
workerData: { posts },
});
worker.once('message', resolve);
worker.once('error', reject);
});
}In the server's /stats route call await calculateStats(await loadPosts()) — the counting runs in a separate thread, and other visitors notice no delay.
When a worker is needed
Workers are for CPU work: compressing images, parsing huge JSON, complex calculations. They are not needed for reading files or network requests — those do not block the event loop anyway.
Common mistake
A worker receives a copy of the data, not a reference to it. Change an object inside the worker and it stays the same in the main thread:
// src/workers/stats.js
workerData.posts.push({ title: 'New' }); // the main thread will not see thisAlways return the result as a message with parentPort.postMessage().