HostiServer
2026-05-31 20:10
Node.js and NPM in 2026: Event Loop, Production Stack, PHP & Python Comparison
Node.js and NPM: from "Hello World" to high-load systems
If your backend "stutters" on every database query — the user won't wait. They'll go to a competitor. Node.js has become the standard for building fast, scalable services, and NPM is the tool that lets you avoid writing from scratch what the community has already built and tested.
A typical scenario: you're launching an API for a mobile app or a monitoring service. A traditional synchronous stack processes requests sequentially — while one waits for a database response, the rest sit in a queue. Node.js works differently: it sends a query to the database, doesn't wait, moves on to the next task, and comes back to the result when it's ready. On a VPS with 4 vCPU, an Express app on Node.js delivers 1,200–1,500 RPS — that's 4–5x more than PHP-FPM with Laravel on the same hardware.
What is Node.js
Node.js is not a programming language — it's a runtime environment. It takes the V8 engine (the same one used in Google Chrome) and lets JavaScript run on the server.
Non-blocking I/O. Node.js doesn't stop while a file is being written to disk or while waiting for a response from an external API. It queues the task and moves to the next one. When the result is ready — it comes back to it.
Scalability. Ideal for microservices, chats, streaming, and real-time systems. Uber, Netflix, PayPal — all built on Node.js not because it's trendy, but because the Event Loop architecture lets a single process handle thousands of simultaneous connections without creating a separate thread for each.
Unified stack. JavaScript on both frontend and backend. This isn't just convenience — it means fewer context switches for developers, shared utilities between client and server, and a single package.json for the whole project in a monorepo approach.
Event Loop: the core of Node.js performance
To understand why Node.js is fast at I/O tasks, you need to understand the Event Loop. This isn't an abstraction — it's a concrete mechanism that determines how every line of your code executes.
The diagram shows three components working together:
Call Stack — the call stack. Functions currently being executed go here. JavaScript is single-threaded, so there's only one Call Stack — functions execute one at a time.
WebAPIs / libuv — when code hits an asynchronous operation (database query, file read, setTimeout), it gets handed off here. The Call Stack doesn't wait — it's free for the next task.
Callback Queue — when an asynchronous operation completes, its callback enters the queue. The Event Loop constantly checks: if the Call Stack is empty — it takes the next callback from the queue and pushes it onto the stack for execution.
The result: while one request waits for a PostgreSQL response (that's 5–50 ms), Node.js manages to handle hundreds of other requests. That's why a single Node.js process handles loads that would require dozens of threads in a traditional "thread-per-request" model.
🚨 The main Event Loop trap: If you put a heavy synchronous operation on the Call Stack — for example, generating a 500-page PDF or processing an image — the entire Event Loop stops. All other requests wait until that operation finishes. That's why CPU-bound tasks in Node.js are offloaded to Worker Threads or separate processes via BullMQ.
NPM: the world's largest package registry
NPM (Node Package Manager) is a dependency manager that installs alongside Node.js. As of 2026, the NPM registry contains over 2.5 million packages — the largest software ecosystem in history.
Instead of spending a week writing an authentication system or an image processor — one terminal command:
# API framework
npm install express
# Input validation and sanitization
npm install joi
# ORM for PostgreSQL
npm install prisma
# Environment variables
npm install dotenv
# HTTP header protection
npm install helmet
package.json and package-lock.json
package.json is your project's "passport." It lists all dependencies with version ranges (e.g., "express": "^4.18.0" — any 4.x from 4.18 and up).
package-lock.json locks the exact versions of every package and all its dependencies. Without it, two developers can get different library versions from the same package.json — a source of "works on my machine, not on the server" bugs. The lock file should always be committed to git.
npx — execute without installing
npx is a separate tool often confused with npm. It runs packages without global installation. Instead of npm install -g create-next-app && create-next-app — just npx create-next-app. The package is downloaded, executed, and doesn't remain on the system.
NPM package security
Supply chain attacks via NPM are a real threat. In 2024–2025, there were several high-profile cases where popular packages contained malicious code that stole tokens or crypto keys. Beyond dependency vulnerabilities, don't forget about your own code security — SQL injections remain one of the most common backend attacks. Basic NPM protection rules:
npm audit— checks dependencies for known vulnerabilities. Run it after everynpm install- Don't install packages with <100 weekly downloads without reviewing the code
- Always commit package-lock.json — it locks integrity hashes for every package
npm ciinstead ofnpm installon CI/CD — it installs exactly what's in the lock file, without "updating ranges"
Node.js vs NPM: the difference
In short: Node.js is the engine. NPM is the parts store for that engine.
| Component | What it does | Analogy |
|---|---|---|
| Node.js | Executes JavaScript code on the server, works with network, files, memory | Car engine |
| NPM | Manages dependencies — installs, updates, removes libraries | Parts store |
| npx | Runs packages one-time without installation | Test drive |
Installing Node.js automatically gives you both NPM and npx. Check versions:
node -v # v22.14.0 (LTS, recommended as of May 2026)
npm -v # 10.x
npx -v # 10.x (comes with npm)
From zero to a working API in 5 minutes
1. Installation via NVM
NVM (Node Version Manager) is the proper way to install Node.js. It lets you keep multiple versions simultaneously and switch between them without breaking the system. Most importantly — no sudo needed for global packages, which eliminates an entire class of permission issues.
# Install NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
# Install Node.js 22 LTS
nvm install 22
# Verify
node -v # v22.14.0
npm -v # 10.x
2. Project initialization
# Create package.json with default settings
npm init -y
3. First server on Express
npm install express
Create app.js:
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('API is running'));
app.get('/health', (req, res) => {
res.json({ status: 'ok', uptime: process.uptime() });
});
app.listen(3000, () => console.log('Server started on port 3000'));
The health-check endpoint isn't decoration. It's needed for monitoring: PM2, Nginx, or an external service check whether the application is alive and how long it's been running.
Production: Nginx + PM2 + Node.js
Running node app.js in the terminal is development. For production, you need a stack that provides auto-restart, clustering, reverse proxy, and logging. Our standard stack: Nginx in front of Node.js as a reverse proxy, PM2 as the process manager.
PM2: process manager
# Global PM2 installation
npm install -g pm2
The ecosystem.config.js file defines how the application runs:
module.exports = {
apps: [{
name: "node-app",
script: "./dist/main.js",
instances: "max", // Uses all CPU cores
exec_mode: "cluster", // Cluster mode for load distribution
env: {
NODE_ENV: "production",
PORT: 3000
},
error_file: "./logs/err.log",
out_file: "./logs/out.log",
log_date_format: "YYYY-MM-DD HH:mm:ss"
}]
}
What PM2 provides:
- Auto-restart — if a process crashes due to an error, PM2 brings it back in a second
- Cluster mode — distributes load across all CPU cores. On a 4-core VPS, that's 4 processes instead of one
- Logging — stdout and stderr are saved to files with timestamps
- Monitoring —
pm2 monitshows CPU, RAM, Event Loop Latency in real time
Here's what pm2 list looks like on a working production server:
And these are the details of a specific process via pm2 describe — Node.js version 22.14.0, production environment, log paths, uptime:
Nginx as reverse proxy
Nginx sits in front of Node.js and handles SSL termination, compression, static file caching, and proxies requests to the application. For a detailed comparison of Nginx and Apache, see our Nginx vs Apache article. Here's a minimal config for Node.js:
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
The Upgrade and Connection lines enable WebSocket support. Without them, real-time functionality (chats, notifications) won't work. X-Real-IP passes the client's real IP address — without it, Node.js sees all requests as coming from 127.0.0.1. To protect your API from overloads, you should also configure DDoS protection at the Nginx and network level.
⚠️ Golden rule of Node.js: Don't store state inside the process. Cache in a global variable, sessions in memory, counters in an object — all of this disappears on restart, isn't shared between instances in cluster mode, and eats RAM without control. Move state to Redis, a database, or S3. A Node.js process should be stateless — then it can be restarted, scaled, and updated without consequences.
Case: eCommerce API on NestJS
A RESTful API for a mobile app and web storefront. Stack: NestJS (TypeScript framework on top of Express), PostgreSQL, Redis. While the examples in this article use plain JavaScript — for real production projects we recommend TypeScript. Type checking catches errors before the code even runs, and NestJS was designed for TypeScript from the start.
| Parameter | Value |
|---|---|
| Load | 450–600 RPS during peak hours |
| Active users | ~25,000 per day |
| VPS configuration | 4 vCPU, 8 GB RAM, NVMe disk |
| PM2 mode | Cluster (4 processes) |
Problems discovered:
First — a memory leak. The heap grew by 50–100 MB daily, and within a week a process consumed all RAM. The cause: caching database query results directly in Node.js process memory (an object in a global variable). Every unique query added a new entry, but entries were never removed.
Solution: moved caching to Redis with TTL (time-to-live) on every key. Node.js process memory stabilized at 150–200 MB, Redis held the cache separately and cleaned up expired entries on its own.
Second problem — delays when processing images "on the fly" (resizing thumbnails on upload). Each operation blocked the Event Loop for 200–500 ms — and the entire API froze for other users.
Solution: moved image processing to a BullMQ queue with separate worker processes. The API accepts the image, throws the task into the queue, and returns a response immediately. The worker processes the image in the background and saves the result. Event Loop stays free.
Top 5 Node.js deployment mistakes
We see these mistakes regularly. Each one sooner or later leads to an incident — the only question is when.
| Mistake | Risk | Solution |
|---|---|---|
| Running as root | If the Node app gets compromised — the attacker gains full control over the VPS. More on server security in our SSH Security guide | Create a separate nodeuser, run PM2 under that account |
Running via node app.js | On the first error, the process dies and doesn't come back. Nobody finds out until a customer complains | PM2 with auto-restart and pm2 startup for boot autostart |
| node_modules in Git | Bloated repository (200+ MB), native module compilation errors (bcrypt) when deploying to Linux from Mac | .gitignore + npm ci on CI/CD |
| Secrets in code | Tokens and database passwords "hardcoded" in JS files, end up in git history. Even if deleted later — they remain in commit history | .env file + dotenv + mandatory .env in .gitignore |
| Blocking the Event Loop | PDF generation, image processing in the main thread "hangs" the entire API | Worker Threads or queues (BullMQ) for CPU-bound tasks |
🚨 A note on .env: The .env file with database passwords, API keys, and tokens is one of the most common causes of Node.js project breaches. A developer forgets to add .env to .gitignore, pushes to a public repository — and bots find those secrets within minutes. Check right now: git log --all --full-history -- .env — if the file was ever in git, the secrets are already compromised and all keys need to be rotated.
Performance: Node.js vs PHP vs Python
Internal measurements on a VPS (4 vCPU, 8 GB RAM, NVMe). A simple API endpoint that performs a SELECT from PostgreSQL and returns JSON was tested:
| Stack | RPS (Simple API) | RAM consumption | Comment |
|---|---|---|---|
| Node.js (Express) | 1,200 — 1,500 | ~100 MB | Optimal for I/O-bound tasks |
| Python (Gunicorn) | 400 — 600 | High | Slower on synchronous requests |
| PHP-FPM (Laravel) | 200 — 300 | Medium | Requires fine-tuning of Opcache |
Node.js dominates on I/O tasks — APIs, WebSocket, streaming. But that doesn't mean PHP or Python are "bad." Laravel wins in development speed for CRUD apps, Python is irreplaceable for ML tasks. Stack choice is determined by the task, not by benchmarks.
When Node.js isn't the best choice
Node.js is single-threaded by nature. CPU-bound tasks are where it falls behind:
- Heavy mathematical computations, ML inference — Python with NumPy or Go will be faster here
- Video/audio processing — ffmpeg is better run as a separate process, not through a Node.js binding
- Compressing large files — zlib in Node.js blocks the Event Loop on large volumes
The rule: if the task "waits" (I/O) — Node.js is ideal. If the task "computes" (CPU) — there are better tools, or offload to Worker Threads.
Docker or PM2: what to choose for deployment
Two approaches to running Node.js in production. Both work, but for different situations:
| Criterion | PM2 (bare) | Docker |
|---|---|---|
| Setup complexity | Simpler — one config | More complex — Dockerfile, compose |
| Performance | Native, no overhead | Minimal network overhead |
| Isolation | OS user level | Full filesystem isolation |
| CI/CD | Requires scripts | GitHub Actions / GitLab CI out of the box |
| Microservices | Hard to manage | Docker Compose / Kubernetes |
PM2 — for monolithic projects where you need maximum performance with minimal overhead. Easier to maintain for small teams.
Docker — for microservice architectures, complex dependencies (different library versions for different services), or when you're using CI/CD for automated deployment.
What about Deno and Bun?
Deno (from Node.js creator Ryan Dahl) and Bun are two alternative runtime environments that appeared in 2020–2023.
Deno fixes architectural decisions that the author himself considers mistakes: built-in TypeScript support, modules via URL instead of node_modules, permissions by default (the process has no filesystem access unless you explicitly allow it). Deno 2.0 (2024) added NPM package compatibility, removing the main barrier to migration.
Bun — built on Zig instead of C++, focused on speed. Built-in bundler, test runner, and package manager. Dependency installation speed comparison is one of the hottest topics among developers in 2026:
| Operation | npm | bun | Difference |
|---|---|---|---|
| install (cold, no cache) | ~35 s | ~3 s | ~10x faster |
| install (cached) | ~15 s | ~1 s | ~15x faster |
| TypeScript execution | Requires tsc/tsx | Natively | No extra steps |
Reality as of 2026: Node.js still holds 95%+ of the server-side JavaScript market. Deno and Bun are interesting projects with specific advantages, but Node.js's ecosystem, stability, documentation, and number of production deployments remain unmatched. For a new project — try Bun or Deno. For a production API with paying customers — Node.js remains the safe choice.
🚀 Deploying a Node.js project?
A VPS with Nginx + PM2 configuration support is the optimal foundation for Node.js in production.
💻 Cloud (VPS) Hosting
- From $19.95/mo — Start small, scale as needed
- KVM virtualization — Guaranteed resources, no overselling
- Node.js 22 LTS — Installed via NVM, PM2 configured
- 24/7 support — <10 min response
🖥️ Dedicated Servers
- From $90/mo — A dedicated server for your project
- Full root access — Any stack without restrictions
- 99.9% uptime — SLA guarantee
- DDoS protection — Included
- Free migration — From old hosting
💬 Not sure which option you need?
💬 Contact us and we'll help you figure it out!
Frequently Asked Questions
- Which Node.js version to choose in 2026?
Node.js 22.x LTS is the default choice. It entered Long Term Support status in fall 2025 and will be supported through 2027. For conservative projects where maximum stability matters — 20.x LTS (supported through 2026). Install via NVM — it lets you keep multiple versions simultaneously and switch between them.
- Why is NVM better than system Node.js?
System Node.js (via apt/yum) has three problems: one version for the entire system, sudo required for global packages (security risk), and updates depend on the OS repository which often lags months behind. NVM solves all of this — you install any version in your home directory without root access.
- Express, Fastify, NestJS — which to choose for an API?
Express — simplest start, largest middleware ecosystem. Fastify — 2–3x faster than Express in benchmarks, better validation via JSON Schema. NestJS — TypeScript-first framework with Angular-like architecture (modules, DI, decorators), suited for large enterprise projects. For an MVP — Express. For a high-load API — Fastify. For a team of 5+ developers — NestJS.
- How to monitor Node.js in production?
PM2 with built-in monitoring is the minimum:
pm2 monitshows CPU, RAM, Event Loop Latency in real time. For more serious projects — Prometheus + Grafana (metrics), Sentry (errors), or Datadog (all-in-one). Key metrics: Event Loop Latency (should be <50 ms), Heap Usage (shouldn't grow indefinitely), and Active Handles.
- Can Node.js run without Nginx?
Technically — yes, Node.js can listen on port 80/443 directly. In practice — you shouldn't. Nginx handles SSL termination, compression (gzip/brotli), static caching, and slow connection protection (slowloris) much better. Node.js should focus on business logic, not serving HTTP connections.
- Docker or PM2 — which is better for deployment?
For a monolithic project with a small team — PM2 is simpler and faster: one config, native performance, minimal overhead. For microservices, complex dependencies, or when you have a CI/CD pipeline (GitHub Actions, GitLab CI) — Docker. On a VPS, both options work without restrictions — full root access lets you configure any stack.