Typescript Workers in NodeJS
12 February 2025
Updated: 19 February 2025
Using worker threads with Typescript is pretty straightforward, this probably depends a bit on your setup but the below idea seems to work for me
Single File Worker
In NodeJS we can use a sort of self-referrential worker such that depending on the context in which the file is loaded the behavior will differ, for example:
1import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";2
3// the worker is the current file4const workerFileName = __filename5
6// define a type for the data we want to share7type WorkerData = {8 id: number,9 name: string10}11
12// check if we're on the main or worker thread13if (isMainThread) {14 // launch a worker thread15 const worker = new Promise((resolve, reject) => {16 const worker = new Worker(workerFileName, {17 // pass data to the worker18 workerData: {19 id: "my-worker",20 name: "Bob"21 }22 })23
24 worker.addListener("message", (result) => resolve(result))25 worker.addListener("error", reject)26 })27
28 worker.then(console.log)29} else {30 // do stuff for the worker thread31 console.log("Running in worker", workerData as WorkerData)32 const message = `Hello ${workerData.name} from worker`33
34 // return back to the parent thread35 parentPort?.postMessage({ message })36}
In the above file we can see a few things that are important to using workers:
- A check is done using
isMainThread
to determine if the file is being executed as a worker. the handling for the worker is based on this - A reference to the file which should be run in the worker thread, the
__filename
variable is defined by NodeJS and holds the name of the curent file - this will be the compiled file name so we don’t need to figure that out on our own - A worker is created using
new Worker
, since the worker is an event based API in the above example it’s being wrapped in aPromise
- Data can be passed to the worker via the
workerData
property, this will then be put into theworkerData
variable fromnode:worker_threads
in the worker thread - Data is sent back to the worker using
parentPort.postMessage
. This is the same even that’s being listened for thePromise
to resolve inworker.addListener("message", resolve)
Multi File Worker
Extending the above to work with multiple files is pretty simple, the only additional trick we need is to export the __filename
from our worker file to the place where we want to construct the worker
So, the updated code can then be broken into two files as follows:
First, the worker is pretty similar to the one above, except now we export the __filename
as workerFileName
1import { isMainThread, parentPort, workerData } from "node:worker_threads";2
3export const workerFileName = __filename4
5export type WorkerData = {6 id: number,7 name: string8}9
10if (!isMainThread) {11 console.log("Running in worker", workerData)12 const message = `Hello ${workerData.name} from worker`13 parentPort?.postMessage({ message })14} else {15 console.log("Worker was run in the main thread")16}
The main.ts
file then makes use of this exported file name to invoke the worker. Note that this is now just a normal script and will be executed in the main thread as normal
1import { Worker } from "node:worker_threads"2
3// import the worker file name from where we defined the worker4import { workerFileName } from "./worker"5
6const workers = [1, 2, 3, 4].map(id => new Promise((resolve, reject) => {7 const worker = new Worker(workerFileName, {8 workerData: {9 id,10 name: `Bob ${id}`11 }12 })13
14 worker.addListener("message", (result) => resolve({ id, result }))15 worker.addListener("error", reject)16}))17
18async function main() {19 const results = await Promise.all(workers)20 console.log(results)21}22
23main()
Uncompiled Worker Using ts-node
When executing workers it’s also possible to execute the Typescript file directly instead of relying on the compilation. This can be useful for cases where the Javascript code is compiled to a single file or in situations where you don’t have control over the execution environment
Executing a Typescript worker can be done by using ts-node
when running the worker, an example of this is:
1const worker = new Worker("my-worker.ts", {2 execArgv: ["-r", "ts-node/register", "--no-warnings"],3 workerData: {4 id5 }6 })7
8// do stuff with the worker