Open-source project
piscinajs/piscina avatar
piscinajs/piscina

Piscina: a Node.js worker thread pool that runs tasks off the main thread

A fast, efficient Node.js Worker Thread Pool implementation

5,216 stars174 forksTypeScriptNOASSERTION

At a glance

What is it?
Piscina wraps Node's worker_threads module in a managed pool with task queues, cancellation and run-time statistics. It suits server-side workloads that need CPU work off the event loop, and it is a poor fit for short jobs where the message-passing overhead dominates.
Who is it for?
Adopt Piscina if you have CPU-bound work in a Node.js server and want the pool managed for you, with cancellation, backpressure and per-task statistics built in. Do not adopt it for trivial tasks or for workloads that are already I/O-bound, because the thread messaging costs more than the work saves.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem Piscina solves for Node.js servers

Node.js runs JavaScript on a single main thread. Anything CPU-heavy on that thread blocks the event loop, so every other request waits. The worker_threads module exists to move work off the main thread, but using it directly means writing your own pool: spawning workers, tracking which are idle, queueing tasks when all are busy, handling worker crashes, and shutting everything down cleanly. Piscina is that layer, packaged as a library. You give it a filename that exports a handler function, call run() with the task payload, and it decides which worker picks the job up. The README describes it as covering both fixed-task and variable-task scenarios, which is the real distinction: some services always run the same function, others dispatch by name to one of several exported handlers. The intended audience is backend engineers running Node.js services where a request triggers something like image resizing, hashing, parsing or numeric work. The README states the package targets Node.js 24.x and higher, while the package.json in the repository declares an engines field of node >=22.x. That discrepancy between the README and the manifest is worth resolving against whichever version you actually install.

How the pool dispatches work to worker threads

A Piscina instance is constructed with a filename pointing at a worker module. That module exports a function, which may be synchronous, async, or a Promise that resolves to the handler. The README notes that a worker is not considered ready until Piscina has loaded it and acquired a reference to the exported handler, and that you can delay readiness by exporting a Promise instead. That matters when a worker needs to open a database connection or compile a WASM module before it can serve anything. Tasks are submitted with run(task, options). The options can carry a name to select one of several exported handlers, and a signal for cancellation. Cancellation accepts either an AbortController or any EventEmitter that emits an abort event, and the README shows both forms. Results come back as a resolved Promise. The pool exposes readonly properties for operational visibility: completed, duration, runTime, waitTime, threads, idleThreads, queueSize and utilization. Those are the numbers you would feed into metrics, and the README lists them as first-class API rather than internals. There are also events: error, drain, needsDrain and message. The drain and needsDrain pair is how you detect queue pressure before it becomes a problem, and the README has a dedicated Queue Pressure and Idle Threads note in its performance section.

Installing Piscina and running a first task

Piscina is published on npm under the name piscina. Install it into an existing project. The README's example uses CommonJS, and the package exports both a require entry point and an ESM wrapper, so either module system works.

bash
npm install piscina

Create a worker file that exports the function to run. This is the README's addition example, and the export can be a plain function, an async function, or a Promise.

js
module.exports = ({ a, b }) => {
  return a + b;
};

Then construct the pool in your main file, pointing filename at the absolute path of that worker, and call run with the payload. The README's example logs 10.

js
const path = require("path");
const Piscina = require("piscina");

const piscina = new Piscina({
  filename: path.resolve(__dirname, "worker.js"),
});

(async function () {
  const result = await piscina.run({ a: 4, b: 6 });
  console.log(result); // Prints 10
})();

For ESM, the README requires the filename to be a file:// URL, which you get from import.meta.url. The same README example builds it with new URL("./worker.mjs", import.meta.url).href. If you export several functions from one file, attach them as properties of the default export and pass { name: "add" } as the second argument to run. When you are done, call close() to stop accepting tasks, or destroy() to terminate the workers.

Where Piscina is the wrong tool

The cost of moving a task to a worker thread is a structured clone of the payload plus a message round trip. For a function that adds two numbers, that cost dwarfs the arithmetic, which is why the README's own example is illustrative rather than a recommendation. If your work is I/O-bound, meaning it waits on a network socket or the filesystem, worker threads buy you nothing, because the main thread is not blocked in the first place. Node's async I/O already handles that. Piscina only pays off when the main thread would otherwise be busy computing. The README also carries a Current Limitations section, described as things the maintainers are working on or would like help with. That section is the honest inventory of what does not work yet, and it should be read before you design around the library. There is a further constraint the README addresses directly: out-of-scope asynchronous code. Asynchronous work started inside a worker that is not awaited as part of the task result is not tracked by the pool, so a worker can appear idle while it is still doing something in the background. That is a real failure mode for anyone who spawns a timer or a fire-and-forget promise inside a handler. Finally, memory limits are enforced per worker, so a task that allocates aggressively can still take down its worker and be retried or reported as an error depending on your handling.

Piscina against running node:worker_threads yourself

The direct alternative is Node's built-in worker_threads module, with no dependency at all. The difference is not capability but bookkeeping. With worker_threads you create a Worker per thread, attach message and error listeners, and maintain your own structure mapping idle workers to pending tasks. You decide what happens when all workers are busy: reject, queue, or spawn more. You decide what happens when a worker throws: replace it or let the pool shrink. Piscina makes those decisions for you and exposes the queue as a configurable object. Its Custom Task Queues section documents built-in queues and shows a FixedQueue example, so you can replace the default scheduling policy when your workload has a known shape. The trade-off is control versus effort. A team with a very specific scheduling need, or one that wants zero dependencies in a hot path, can write a smaller pool than Piscina in a few hundred lines and understand every branch. A team that wants cancellation, per-task wait-time statistics and backpressure signals without building them will spend less time on the pool and more on the actual work. Piscina is written in TypeScript and ships type definitions through its exports field, so TypeScript consumers get types without a separate @types package.

Maintenance, licensing and the v6 release candidate

The repository is not archived, and its last push was on 2026-09-21, two days before this writing, so the project is being worked on. Three release lines were published on 2026-08-28: v6.0.0-rc.5, v5.3.2 and v4.9.4. The presence of a release candidate for a major version alongside stable patch releases on two older lines tells you the maintainers are maintaining the current line while preparing a breaking change. If you adopt today, the stable choice is the v5 line; v6 is a release candidate and its API may still move. The README says the project is MIT licensed, and the repository contains a LICENSE file. The metadata for the repository reports the licence as NOASSERTION, which means the automated classifier could not match the file to a known licence identifier. That is a tooling artifact, not a claim about the terms. If licence terms matter to your organisation, read the LICENSE file in the repository rather than trusting either label; this is not legal advice. Upgrade cost between minor versions should be low, but the v5 to v6 step is a major version and should be treated as one. The README's Node.js version requirement is the first thing to check: the README says Node.js 24.x and higher, while package.json declares engines node >=22.x.

Editorial conclusion

Adopt Piscina if you have CPU-bound work in a Node.js server and want the pool managed for you, with cancellation, backpressure and per-task statistics built in. Do not adopt it for trivial tasks or for workloads that are already I/O-bound, because the thread messaging costs more than the work saves. Before committing, check that your Node.js version matches the engines field in the package you install, and read the Current Limitations section of the README, which lists what the maintainers say is still unfinished.

Frequently asked questions

What is Piscina in Node.js?

Piscina is a worker thread pool implementation for Node.js, written in TypeScript and published on npm as piscina. You point it at a worker file that exports a handler function and submit tasks with run(), and it manages which worker thread executes each task.

How do I install Piscina?

Install it from npm with npm install piscina. The package exports both a CommonJS entry point and an ESM wrapper, so it works with require and with import, and it ships its own TypeScript definitions.

Does Piscina work with ESM and TypeScript?

Yes. The README lists CommonJS, ESM and TypeScript as supported, and shows an ESM example where the worker filename must be a file:// URL built from import.meta.url. The package's exports field points types at ./dist/index.d.ts.

Can I cancel a task running in Piscina?

Yes. Tasks can be canceled with an AbortController or with any EventEmitter that emits an abort event, passed as the signal option to run(). The README shows both forms, and the awaited task rejects when cancellation happens.

Official sources

  1. Issues
  2. piscinajs/piscina on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/piscinajs-piscina.svg)](https://hysenlabs.com/projects/piscinajs-piscina)