When to Use (and Not Use) Node.js
Every technology has a sweet spot. Choosing Node.js because it is trendy — or avoiding it because of a myth — both lead to pain. This page gives you a decision framework grounded in how Node actually works, plus the patterns that let you stretch it beyond its comfort zone.
The one-sentence rule
The reason traces directly back to the single-threaded, non-blocking model: waiting is delegated and essentially free, but computing happens on the one thread that also serves everyone else.
Great fits for Node.js
Use case | Why Node fits |
|---|---|
REST / GraphQL APIs | Mostly I/O: receive request, query DB, serialize JSON, respond |
Real-time apps (chat, presence, games) | Many mostly-idle WebSocket connections — cheap on the event loop |
Microservices | Fast startup, low memory, easy to deploy many small services |
Serverless functions | Quick cold starts on Lambda/Vercel/Cloudflare |
API gateways & proxies | Concurrently fan out to many upstreams and aggregate |
Streaming (data/media/uploads) | Process chunk-by-chunk without buffering everything in memory |
CLIs & build tooling | Vite, webpack, ESLint, Prettier — fast startup, npm ecosystem |
Server-side rendering (SSR) | Next.js, Nuxt, Remix render React/Vue on the server |
Poor fits — and what to do instead
Workload | Verdict | Better approach |
|---|---|---|
Video/image transcoding | Poor on the main thread | Offload to a worker, child process, or a dedicated service (ffmpeg) |
Machine-learning training | Poor | Use Python/GPU; let Node call it as a service |
Heavy scientific computation | Poor | Worker Threads, native add-ons, or another language |
Hard real-time / low-latency control | Poor | GC pauses are non-deterministic — use C/C++/Rust |
Large in-process data crunching | Risky | Stream it, batch it, or move it to a queue/worker |
The offloading patterns
When you do have a CPU-heavy task, these are the standard escape hatches, from lightest to heaviest:
Worker Threads — run the computation on another thread within the same process, sharing memory via
SharedArrayBufferwhen needed. Best for CPU work that is part of the app. See Worker Threads.Child processes — spawn a separate process (even a non-JS program like
ffmpeg) and communicate over stdio/IPC. See Child Processes.Job queues — push the task to a queue (Redis/BullMQ, a broker) and let dedicated worker processes handle it asynchronously, decoupled from the request.
A separate service — extract the heavy part into its own service in whatever language suits it, and have Node call it over HTTP/gRPC.
Offloading a CPU task to a worker thread
import { Worker } from 'node:worker_threads'
function runHeavyTask(input) {
return new Promise((resolve, reject) => {
const worker = new Worker('./heavy-task.js', { workerData: input })
worker.on('message', resolve) // result comes back without blocking the loop
worker.on('error', reject)
})
}
// The main thread stays free to serve other requests.
const result = await runHeavyTask({ size: 10_000_000 })Quick comparison
Scenario | Node.js fit | Why |
|---|---|---|
JSON API over a database | Excellent | I/O-bound, high concurrency |
Real-time chat / presence | Excellent | Event-driven, many idle connections |
Image/video processing | Poor (offload it) | CPU-bound, blocks the loop |
Machine-learning training | Poor | Heavy compute; use Python/GPU |
CLI tool / bundler | Excellent | Fast startup, npm ecosystem |
High-frequency trading core | Poor | GC pauses; needs deterministic latency |
Myths to discard
"Node can not scale" — it scales superbly for I/O. You scale across CPU cores by running multiple processes (cluster / containers), which is normal for any runtime.
"Single-threaded means slow" — for I/O it is often faster than thread-per-request because it avoids context-switch and memory overhead.
"Node is only for small apps" — Netflix, PayPal, Uber, and Walmart run it at massive scale.
"You must rewrite everything in JS" — Node is a great orchestrator; it can shell out to other languages for the parts they do best.
A decision checklist
Is the work mostly waiting on I/O? → Node is a strong choice.
Are there many concurrent connections, many idle? → Node shines.
Do you value shared code/types with a JS front end? → Big win.
Is there a CPU-heavy core step? → Plan to offload it before committing.
Do you need deterministic, sub-millisecond latency? → Consider another runtime.