How Many Requests Per Second Can a Single Node.js Process Handle?
Problem
When I planned to deploy my Node.js application, I wondered: how many requests per second can a single process actually handle? I needed concrete numbers for capacity planning.
Environment
- Node.js 20.x
- Express.js backend
- PostgreSQL database
- Running on Linux server
What happened?
I searched for benchmarks and found that most answers were too generic: “it depends.” So I dug into production experiences from real developers.
Here’s what I found from actual production systems:
| Scenario | Throughput | Context |
|---|---|---|
| Simple API endpoint | 10k+ concurrent users | Single process, minimal logic |
| Moderate business logic | ~120 RPS | 100-120ms/request, 70% CPU |
| SSR with Next.js | Lower 4 digits | Significantly reduced vs API |
| SQS message processing | 100/sec | DB saves, 0.25vCPU, 512MB |
The numbers vary wildly because throughput depends on what your code actually does.
How to estimate your throughput?
Factor #1: Type of Workload
Different workloads have very different capacities:
| Workload Type | Expected RPS | Why |
|---|---|---|
| Static API proxy | 5,000-10,000+ | Minimal CPU, pure I/O |
| Database CRUD | 500-2,000 | Depends on DB latency |
| Business logic heavy | 100-500 | CPU-bound operations |
| SSR/Template rendering | 1,000-3,000 | Synchronous rendering |
| JSON-heavy processing | 200-800 | Synchronous parsing |
Factor #2: Event Loop Health
Node.js handles requests on a single thread via the event loop. When the loop is blocked, all concurrent requests wait.
// BAD: Blocks event loop (synchronous CPU work)app.get('/process', (req, res) => { const result = heavyCalculation(10000000) // Blocks! res.json(result)})
// GOOD: Offload to worker threadapp.get('/process', async (req, res) => { const result = await runInWorkerThread(heavyCalculation, 10000000) res.json(result)})Factor #3: JSON Parsing Impact
One insight I found crucial: JSON parsing is synchronous. Small JSON payloads (under 1KB) are fine, but larger payloads slow things down significantly.
Payload size | Parse time (approx)------------|---------------------< 1KB | Negligible1-10KB | ~1-5ms10-100KB | ~5-50ms> 100KB | Can block event loopWhen should I consider clustering?
Clustering becomes beneficial when:
- CPU utilization consistently >70% on single process
- Request latency increases under load despite available memory
- Event loop lag exceeds 50-100ms
- Business logic is CPU-intensive (image processing, cryptography)
But before adding clustering, I should optimize first.
Monitor Event Loop Lag
const { monitorEventLoopDelay } = require('perf_hooks')
const h = monitorEventLoopDelay()h.enable()
setInterval(() => { console.log(`Event loop lag: min=${h.min}ms, mean=${h.mean}ms, max=${h.max}ms`)}, 5000)If I see lag exceeding 50ms consistently, that’s a signal to either optimize code or consider clustering.
Benchmark Your Actual Application
Synthetic benchmarks don’t reflect real performance. I should test my actual endpoints:
const autocannon = require('autocannon')
async function benchmark() { const result = await autocannon({ url: 'http://localhost:3000/api/your-endpoint', connections: 100, duration: 30 })
console.log(`Requests/sec: ${result.requests.mean}`) console.log(`Latency: ${result.latency.mean}ms`) console.log(`Errors: ${result.errors}`)}
benchmark()The reason
The key reason throughput varies so much is the event loop architecture:
- I/O-bound operations (database queries, API calls) don’t block the loop - high throughput
- CPU-bound operations (JSON parsing, encryption, templating) block the loop - low throughput
- SSR rendering involves synchronous template processing - reduced capacity
This is why “it depends” is actually the honest answer. A simple proxy server can handle 10,000+ connections, but a complex business app might struggle at 100 RPS.
Common Mistakes
| Mistake | Impact | Solution |
|---|---|---|
| Premature clustering | Complexity without benefit | Benchmark first |
| Blocking event loop | All requests hang | Use worker threads |
| Ignoring JSON payload size | Unexpected slowdowns | Stream large payloads |
| Not monitoring event loop | Silent performance degradation | Add monitoring |
| Assuming linear scaling | Misleading capacity plans | Test with real traffic |
Summary
In this post, I explored how many requests a single Node.js process can handle. The key point is: 10,000+ concurrent connections for simple APIs, or 100-500 RPS for typical business logic. Before adding clustering complexity, I should benchmark my actual workload, monitor event loop health, and optimize blocking operations first.
The real answer: test with realistic traffic patterns. Most applications don’t need clustering until they reach hundreds of requests per second with non-trivial processing.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Node.js Event Loop Documentation
- 👨💻 autocannon - HTTP/1.1 benchmarking tool
- 👨💻 Reddit Discussion: Single Node.js Process Scaling
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments