Loading...
Loading...
Node.js backend fundamentals, APIs, runtime concepts, and practical full-stack guidance. Use this page as a focused learning resource for node.js concepts, interview preparation, and practical revision.
Master server-side JavaScript from event loop fundamentals to microservices, Docker, cloud deployment, and AI integration - the most comprehensive Node.js reference for 2026.
Examples
29
Chapters
v22
LTS
Chapter 01
Node.js
JavaScript
Backend
2026
Node.jsis an open-source, cross-platform JavaScript runtime environment built on Google Chrome's V8 JavaScript engine. First released in 2009 by Ryan Dahl, it enables developers to execute JavaScript code outside of a web browser - turning what was once a purely front-end scripting language into a powerful tool for server-side, CLI, scripting, and systems programming. By 2026, Node.js powers an enormous portion of the internet's backend infrastructure, from real-time chat applications to large-scale microservices architectures at companies including Netflix, Walmart, LinkedIn, NASA, and Airbnb.
What fundamentally distinguishes Node.js from traditional server environments such as Apache with PHP, Python/Django, or Java/Spring is itsnon-blocking, event-driven I/O model. Traditional servers spawn a new thread or process per incoming request - a model that consumes significant memory and CPU overhead when thousands of connections arrive simultaneously. Node.js, by contrast, operates on a single thread and uses an event loop to handle concurrent operations without blocking execution. The result is exceptional efficiency for I/O-bound workloads: REST APIs, streaming services, real-time collaboration tools, and microservices.
In 2026, Node.js has matured far beyond its original scope. The currentLTS release, Node.js 22, ships with native TypeScript type-stripping (run.tsfiles directly without compiling), a stable built-in test runner, nativefetchAPI, improved WebAssembly support, a permission model for sandboxing, single-executable application generation, and dramatically improved V8 performance via the Maglev JIT compiler. These additions have made Node.js a more complete, safer, and more productive platform than ever before.
Despite competition from newer runtimes like Deno and Bun, Node.js maintains overwhelming dominance in the market for several compelling reasons. First isecosystem depth- npm hosts over 2.5 million packages accumulated over 15 years, representing an irreplaceable body of community work that no competitor can replicate overnight. Second isuniversal JavaScript- using the same language on frontend and backend eliminates context switching, enables code sharing between client and server, and dramatically accelerates full-stack development. Third isenterprise stability- organisations that have invested in Node.js infrastructure have no compelling reason to migrate when the platform continues to improve and remains actively maintained under the OpenJS Foundation.
The rise of AI-powered applications in 2025-2026 has opened a new chapter for Node.js. The asynchronous, non-blocking model is exceptionally well-suited for backends that orchestrate calls to AI APIs - where most of the time is spent waiting for a remote model to return a response. Node.js can handle thousands of concurrent AI requests without the memory overhead that would cripple a thread-per-request system. First-class SDKs from Anthropic, OpenAI, Google, and others are all available as npm packages, making Node.js the preferred choice for building AI-native applications.
Handle thousands of concurrent connections with minimal memory via the event loop.
Same language front-to-back. Share types, utilities, and validation logic.
The world's largest package registry - npm has a solution for every problem.
Perfect fit for containers, serverless functions, and edge computing.
First-class SDKs for every major LLM provider. Ideal for AI backends.
WebSockets and SSE enable live dashboards, chat, and collaborative apps.
📌 Node.js LTS Policy
Node.js releases LTS (Long-Term Support) versions every October from even-numbered major versions. Node 22 (LTS until April 2027) is the recommended production runtime in 2026. Always use LTS in production for stability and security guarantees.
Chapter 02
The story of Node.js begins in 2008 when Google open-sourced its V8 JavaScript engine. Ryan Dahl, a web developer frustrated with the blocking nature of Apache HTTP Server, saw an opportunity. In 2009, he presented Node.js at JSConf EU - a server built on V8 with a non-blocking I/O model inspired by events-driven networking libraries. The demo was met with standing applause, and within months a community had formed around the project.
In 2010, Isaac Z. Schlueter creatednpm, the Node Package Manager. This was arguably as important as Node itself - npm gave the community a standardized way to share and consume reusable JavaScript modules, sparking an explosion of open-source innovation that continues to this day.
| Year | Version / Event | Key Milestone | | --- | --- | --- | | 2009 | Node.js 0.1 | Initial release by Ryan Dahl; non-blocking I/O on V8 | | 2010 | npm launch | Package manager created by Isaac Z. Schlueter | | 2011 | Node 0.6 | Windows support; cluster module for multi-core usage | | 2014 | io.js fork | Community fork due to slow release cadence; ES6 features | | 2015 | Node 4.0 LTS | io.js merged; Node.js Foundation formed; LTS model adopted | | 2017 | Node 8.0 | Async/await support; N-API for stable native addons | | 2019 | Node 12.0 | V8 7.4; experimental ES Modules; Worker Threads stable | | 2020 | Node 14.0 | Optional chaining; nullish coalescing; Diagnostic Reports | | 2022 | Node 18.0 | Native fetch; built-in test runner; watch mode | | 2023 | Node 20.0 | Stable test runner; permission model; single executable apps | | 2024 | Node 22.0 LTS | Native TS type stripping; Maglev JIT; ESM improvements | | 2025-26 | Node 22.x / 24.x | WASM GC; edge compat; improved AI/streaming tooling |
Deno, created by Ryan Dahl himself in 2018, was designed to fix many of Node's architectural decisions - it has built-in TypeScript, URL-based imports, and a permission model.Bun, written in Zig by Jarred Sumner, appeared in 2022 and achieved remarkable startup speed and npm compatibility. Both have pushed the Node.js team to accelerate improvements. Yet in 2026, neither has meaningfully displaced Node.js in enterprise adoption - the ecosystem, tooling, and community gravity of Node are simply too strong.
Chapter 03
Theevent loopis the beating heart of Node.js and the most important concept to truly understand if you want to write correct, high-performance Node.js applications. Despite JavaScript being single-threaded, the event loop enables Node.js to handle enormous concurrency by never blocking the thread while waiting for I/O operations to complete.
When Node.js starts, it initialises the event loop, processes the input script (running any synchronous code and scheduling async callbacks), and then enters the event loop itself. The loop has multiple sequential phases, each with its own FIFO callback queue. The loop processes all callbacks in each phase before moving to the next. Once all queues are empty and no more callbacks are scheduled, Node.js exits.
Event Loop Phases
┌──────────────────────────────────────────────────┐
│ EVENT LOOP │
│ │
│ ┌─────────────┐ ┌──────────────────────┐ │
│ │ timers │ → │ pending callbacks │ │
│ │ setTimeout │ │ (deferred I/O errs) │ │
│ │ setInterval │ └──────────────────────┘ │
│ └─────────────┘ ↓ │
│ ┌──────────────────────┐ │
│ │ idle / prepare │ │
│ │ (internal Node.js) │ │
│ └──────────────────────┘ │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ poll │ │
│ │ ← retrieve new I/O │ │
│ │ ← execute callbacks │ │
│ └──────────────────────┘ │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ check │ │
│ │ setImmediate() │ │
│ └──────────────────────┘ │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ close callbacks │ │
│ │ socket.on('close') │ │
│ └──────────────────────┘ │
│ │
│ Between EVERY phase: process.nextTick() runs │
│ then Promise microtasks (.then/.catch) │
└──────────────────────────────────────────────────┘
JavaScript
// Understanding execution order is critical for Node.js
console.log('A - sync');
setTimeout(() => console.log('E - setTimeout(0)'), 0);
setImmediate(() => console.log('D - setImmediate'));
Promise.resolve().then(() => console.log('C - Promise microtask'));
process.nextTick(() => console.log('B - nextTick'));
console.log('A2 - still sync');
// Output order:
// A - sync
// A2 - still sync
// B - nextTick (microtask queue, highest priority)
// C - Promise microtask (microtask queue)
// D - setImmediate (check phase)
// E - setTimeout(0) (timers phase - may vary)
⚠️ Never Block the Event Loop
CPU-heavy synchronous work (heavy JSON.parse, large loops, synchronous file reads) blocks the thread and freezes ALL concurrent requests. For CPU-intensive work use Worker Threads. For large files use streams. The event loop must stay free for I/O callbacks to be processed promptly.
JavaScript - Worker Threads
import { Worker, isMainThread, parentPort, workerData }
from 'node:worker_threads';
if (isMainThread) {
// Main thread: offload CPU work to a worker
const runWorker = (data) =>
new Promise((resolve, reject) => {
const w = new Worker(new URL(import.meta.url), { workerData: data });
w.on('message', resolve);
w.on('error', reject);
});
const result = await runWorker({ n: 42 });
console.log(`fib(42) = ${result}`); // 267914296
} else {
// Worker thread: runs in parallel, cannot block main thread
const fib = n => n <= 1 ? n : fib(n - 1) + fib(n - 2);
parentPort.postMessage(fib(workerData.n));
}
Chapter 04
Bash - Installation
# Use fnm (Fast Node Manager) - recommended in 2026
curl -fsSL https://fnm.vercel.app/install | bash
fnm install 22 # Install LTS
fnm use 22
fnm default 22
# Verify
node --version # v22.x.x
npm --version # 10.x.x
# Enable pnpm via corepack (recommended package manager)
corepack enable pnpm
pnpm --version # 9.x.x
# Create project
mkdir my-api && cd my-api
pnpm init
JSON
{
"name": "my-api",
"version": "1.0.0",
"type": "module", // ESM by default in 2026
"engines": { "node": ">=22", "pnpm": ">=9" },
"scripts": {
"start": "node src/server.js",
"dev": "node --watch src/server.js", // built-in watch
"test": "node --test", // built-in test runner
"build": "tsc"
},
"dependencies": {
"express": "^5.0.0",
"zod": "^3.23.0",
"drizzle-orm": "^0.32.0"
},
"devDependencies": {
"typescript": "^5.5.0",
"@types/node": "^22.0.0"
}
}
Directory Tree
my-api/
├── src/
│ ├── server.js # Entry - bind port
│ ├── app.js # Express app, middleware
│ ├── routes/ # Route files per resource
│ │ ├── users.js
│ │ └── orders.js
│ ├── controllers/ # Request handlers
│ ├── services/ # Business logic
│ ├── middleware/ # auth, validate, rate-limit
│ ├── models/ # DB schemas
│ ├── config/ # env vars, constants
│ └── utils/ # Pure helper functions
├── tests/
│ ├── unit/
│ └── integration/
├── .env # NEVER commit
├── .env.example # Commit this template
├── package.json
└── tsconfig.json
Chapter 05
Node.js supports two module systems: the legacyCommonJS (CJS)and the modernECMAScript Modules (ESM). In 2026, ESM is the standard for all new projects. Setting"type": "module"in package.json makes every.jsfile an ES module by default.
JavaScript - CommonJS (legacy)
// math.js - export
const add = (a, b) => a + b;
const PI = 3.14159;
module.exports = { add, PI };
// app.js - import
const { add, PI } = require('./math'); // no extension needed in CJS
const express = require('express');
const path = require('path');
JavaScript - ESM (2026 standard)
// math.js - exportexport constadd= (a, b) => a + b;export constPI=3.14159;export default{add,PI};// app.js - import (file extension required in ESM)import{add,PI}from'./math.js';importexpressfrom'express';importpathfrom'node:path';// node: prefix = best practiceimport{ readFile }from'node:fs/promises';// Dynamic import - lazy load heavy modulesconst{ heavy } =await import('./heavy.js');// __dirname equivalent in ESMimport{ fileURLToPath }from'node:url';const__filename = fileURLToPath(import.meta.url);const__dirname = path.dirname(__filename);// Top-level await - works in ESMconstconfig = JSON.parse(awaitreadFile('./config.json','utf8'));
✅ 2026 Best Practice
Always use"type":"module"for new projects. Prefix Node.js built-ins withnode:(e.g.node:fs,node:path) - this is explicit, avoids naming conflicts with npm packages, and is the official recommendation from the Node.js team.
Chapter 06
Bash - Common Commands
# Install all dependencies from package.json
pnpm install # or: npm install
# Add production dependency
pnpm add express zod drizzle-orm
# Add dev dependency
pnpm add -D typescript @types/node vitest
# Remove package
pnpm remove lodash
# Check for vulnerabilities
npm audit
npm audit fix
# Update all packages safely
pnpm update
# Check for outdated (including majors)
npx npm-check-updates -u
# List dependency tree
npm ls --depth=2
# Run script in all workspace packages (monorepo)
pnpm -r build
# ALWAYS commit: package-lock.json OR pnpm-lock.yaml
# NEVER commit: node_modules/
| Package | Category | Purpose | | --- | --- | --- | | express / fastify / hono | Web Framework | HTTP routing, middleware pipeline | | zod | Validation | Runtime schema validation + TypeScript inference | | drizzle-orm / prisma | ORM | Type-safe database access layer | | ioredis | Cache / Queue | Redis client for caching and pub/sub | | jsonwebtoken / jose | Auth | JWT creation, signing, verification | | argon2 | Security | Password hashing (recommended in 2026) | | helmet | Security | Sets 15+ security HTTP response headers | | cors | Security | Cross-Origin Resource Sharing configuration | | pino / winston | Logging | Structured, high-performance logging | | vitest | Testing | Fast unit/integration test runner (ESM native) | | bullmq | Jobs / Queue | Redis-backed job queue for background work | | socket.io | Real-Time | WebSocket abstraction with fallbacks | | @anthropic-ai/sdk | AI | Anthropic Claude API client | | openai | AI | OpenAI API client |
Chapter 07
Asynchronous programming is the defining characteristic of Node.js development. The language and platform have evolved through three distinct patterns for handling async operations. In 2026,async/awaitis the universal standard, but understanding the evolution is essential for reading older code and for deep debugging.
JavaScript
import fs from 'node:fs';
// Error-first callback convention: callback(err, result)
fs.readFile('./data.json', 'utf8', (err, data) => {
if (err) { console.error(err); return; }
// "Callback hell" - deeply nested operations are unreadable
fs.writeFile('./out.json', data, (err) => {
if (err) { console.error(err); return; }
console.log('done');
});
});
JavaScript
import { readFile, writeFile } from 'node:fs/promises';
// Chaining
readFile('./data.json', 'utf8')
.then(data => writeFile('./out.json', data))
.then(() => console.log('done'))
.catch(err => console.error(err));
// Parallel execution
const [users, orders] = await Promise.all([
fetchUsers(),
fetchOrders()
]);
// All results even when some fail
const results = await Promise.allSettled([api1(), api2()]);
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value);
else console.error(r.reason);
});
JavaScript
import { readFile, writeFile } from 'node:fs/promises';
async function processFile() {
try {
const raw = await readFile('./data.json', 'utf8');
const parsed = JSON.parse(raw);
parsed.updatedAt = new Date().toISOString();
await writeFile('./out.json', JSON.stringify(parsed, null, 2));
console.log('Processed successfully');
} catch (err) {
console.error('Failed:', err.message);
throw err;
}
}
// Parallel with async/await - MUCH faster than sequential awaits
async function getDashboard(userId) {
const [user, orders, notifications] = await Promise.all([
getUser(userId),
getOrders(userId),
getNotifs(userId)
]);
return { user, orders, notifications };
}
// Timeout utility
const withTimeout = (promise, ms) =>
Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms)
)
]);
⚠️ Avoid Sequential Await in Loops
for...ofwithawaitinside issequential- each iteration waits for the previous one to complete. For parallel execution useawait Promise.all(items.map(fn)). The difference is significant: 10 API calls at 100ms each take 1,000ms sequentially but only ~100ms in parallel.
Chapter 08
Streamsare one of the most distinctive and powerful features of Node.js. A stream processes data incrementally - piece by piece - without loading the entire dataset into memory at once. This makes them essential for working with large files, network data, or any source that produces data over time. HTTP requests and responses, file reads and writes, compression, and encryption are all implemented as streams in Node.js internals.
| Type | Description | Example | | --- | --- | --- | | Readable | Source you read from | fs.createReadStream, HTTP req body | | Writable | Destination you write to | fs.createWriteStream, HTTP response | | Duplex | Both readable and writable | TCP socket, net.Socket | | Transform | Modifies data in transit | zlib.createGzip, crypto cipher |
JavaScript - Streams
import fs from 'node:fs';
import zlib from 'node:zlib';
import { pipeline } from 'node:stream/promises';
import { Transform } from 'node:stream';
// Compress a large file without reading it all into memory
await pipeline(
fs.createReadStream('./bigfile.csv'),
zlib.createGzip(),
fs.createWriteStream('./bigfile.csv.gz')
);
// Custom Transform: uppercase every line
class UpperCase extends Transform {
_transform(chunk, _enc, cb) {
this.push(chunk.toString().toUpperCase());
cb();
}
}
await pipeline(
fs.createReadStream('./in.txt'),
new UpperCase(),
fs.createWriteStream('./out.txt')
);
// Async generator as readable stream (elegant pattern)
import { Readable } from 'node:stream';
async function* generateLines(n) {
for (let i = 0; i < n; i++) yield `Line ${i + 1}\n`;
}
await pipeline(
Readable.from(generateLines(10_000)),
fs.createWriteStream('./lines.txt')
);
Chapter 09
JavaScript
import {
readFile, writeFile, appendFile, unlink,
mkdir, rm, readdir, stat, rename, copyFile
} from 'node:fs/promises';
import path from 'node:path';
// Read file
const text = await readFile('./config.json', 'utf8');
const cfg = JSON.parse(text);
// Write file (creates or overwrites)
await writeFile('./out.json', JSON.stringify(data, null, 2));
// Create nested directories safely
await mkdir('./uploads/imgs/thumbs', { recursive: true });
// List directory
const files = await readdir('./src', { withFileTypes: true });
const jsOnly = files.filter(f => f.isFile() && f.name.endsWith('.js'));
// File metadata
const info = await stat('./package.json');
console.log(info.size, info.mtime);
// Delete recursively
await rm('./tmp', { recursive: true, force: true });
// Path utilities - ALWAYS use path.join, never string concat
path.join(__dirname, 'public', 'index.html');
path.extname('photo.jpg'); // '.jpg'
path.basename('/usr/local/bin'); // 'bin'
path.dirname('/usr/local/bin'); // '/usr/local'
path.resolve('./src', 'app.js'); // absolute path
Chapter 10
Before reaching for Express or Fastify, every Node.js developer should understand thenode:httpmodule - the foundation all frameworks are built on. This knowledge helps you understand what frameworks do under the hood and debug framework-related issues effectively.
JavaScript - Bare HTTP Server
import { createServer } from 'node:http';
const server = createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
if (req.method === 'GET' && url.pathname === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', ts: Date.now() }));
return;
}
if (req.method === 'POST' && url.pathname === '/echo') {
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
const body = JSON.parse(Buffer.concat(chunks).toString());
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ echo: body }));
return;
}
res.writeHead(404);
res.end(JSON.stringify({ error: 'Not found' }));
});
server.listen(3000, () => console.log('Listening on :3000'));
Chapter 11
Express.js 5(stable since late 2024) is the current major version, bringing native async/await support to route handlers, improved path matching with thepath-to-regexpv8 upgrade, better error handling semantics, and removal of deprecated APIs. Express remains the most widely deployed Node.js web framework in 2026, with Fastify gaining ground for performance-critical services.
JavaScript - Express 5 App
// src/app.js
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
import { usersRouter } from './routes/users.js';
import { errorHandler } from './middleware/errorHandler.js';
export const app = express();
// ── Security headers
app.use(helmet());
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') }));
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
// ── Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// ── Routes
app.use('/api/v1/users', usersRouter);
app.get('/health', (req, res) =>
res.json({ status: 'ok', uptime: process.uptime() })
);
// ── 404
app.use((req, res) =>
res.status(404).json({ error: `${req.method} ${req.path} not found` })
);
// ── Error handler (4 params - must be last)
app.use(errorHandler);
JavaScript - Router
// src/routes/users.js
import { Router } from 'express';
import * as ctrl from '../controllers/userController.js';
import { auth } from '../middleware/auth.js';
import { validate } from '../middleware/validate.js';
import { CreateUserSchema } from '../schemas/user.js';
export const usersRouter = Router();
usersRouter.get('/', auth, ctrl.list);
usersRouter.get('/:id', auth, ctrl.get);
usersRouter.post('/', validate(CreateUserSchema), ctrl.create);
usersRouter.put('/:id', auth, ctrl.update);
usersRouter.delete('/:id', auth, ctrl.remove);
usersRouter.route('/:id/profile')
.get(auth, ctrl.getProfile)
.patch(auth, ctrl.updateProfile);
Chapter 12
JavaScript - Middleware Examples
// ── Request logger
export const logger = (req, res, next) => {
const t = Date.now();
res.on('finish', () =>
console.log(`${req.method} ${req.path} → ${res.statusCode} (${Date.now()-t}ms)`)
);
next();
};
// ── Zod body validator factory
export const validate = (schema) => (req, res, next) => {
const r = schema.safeParse(req.body);
if (!r.success) return res.status(400).json({
error: 'Validation failed',
details: r.error.flatten()
});
req.body = r.data; // replace with coerced + validated data
next();
};
// ── Custom AppError class
export class AppError extends Error {
constructor(msg, status = 500, code = 'INTERNAL_ERROR') {
super(msg);
this.statusCode = status;
this.code = code;
this.isOperational = true;
}
}
// ── Global error handler (4 params = error-handling middleware)
export const errorHandler = (err, req, res, next) => {
const isDev = process.env.NODE_ENV === 'development';
console.error('[ERR]', err.message);
res.status(err.statusCode ?? 500).json({
error: err.code ?? 'INTERNAL_ERROR',
message: err.isOperational ? err.message : 'Something went wrong',
...(isDev && { stack: err.stack })
});
};
Chapter 13
| Method | Action | URL | Status | | --- | --- | --- | --- | | GET | List resources | /api/v1/users | 200 + array | | GET | Get single | /api/v1/users/:id | 200 | 404 | | POST | Create | /api/v1/users | 201 + object | | PUT | Replace | /api/v1/users/:id | 200 | 404 | | PATCH | Partial update | /api/v1/users/:id | 200 | 404 | | DELETE | Remove | /api/v1/users/:id | 204 | 404 |
JavaScript - Controller Pattern
// src/controllers/userController.jsimport{ db }from'../db/index.js';import{ AppError }from'../middleware/errorHandler.js';export constlist =async(req, res) => {const{ page =1, limit =20, q } = req.query;constoffset = (Number(page) -1) * Number(limit);const{ rows, total } =awaitdb.listUsers({ offset, limit: +limit, q }); res.json({ data: rows, pagination: { page: +page, limit: +limit, total, pages: Math.ceil(total / +limit) } }); };export constget =async(req, res) => {constuser =awaitdb.findById(req.params.id);if(!user)throw newAppError('User not found',404,'USER_NOT_FOUND'); res.json({ data: user }); };export constcreate =async(req, res) => {constuser =awaitdb.createUser(req.body); res.status(201).json({ data: user }); };export constremove =async(req, res) => {constok =awaitdb.softDelete(req.params.id);if(!ok)throw newAppError('User not found',404); res.status(204).end(); };
Chapter 14
JavaScript - JWT Auth Module
import { sign, verify } from 'jsonwebtoken';
import argon2 from 'argon2';
import { AppError } from './errorHandler.js';
const SECRET = process.env.JWT_SECRET;
const REFRESH = process.env.JWT_REFRESH_SECRET;
// Hash password with Argon2id (recommended 2026)
export const hashPwd = p => argon2.hash(p, { type: argon2.argon2id });
export const verifyPwd = (h, p) => argon2.verify(h, p);
// Issue access + refresh token pair
export const issueTokens = (userId, role) => ({
accessToken: sign({ sub: userId, role }, SECRET, { expiresIn: '15m' }),
refreshToken: sign({ sub: userId }, REFRESH, { expiresIn: '7d' })
});
// Auth middleware
export const auth = (req, res, next) => {
const hdr = req.headers.authorization;
if (!hdr?.startsWith('Bearer '))
throw new AppError('No token', 401);
try {
req.user = verify(hdr.slice(7), SECRET);
next();
} catch {
throw new AppError('Invalid or expired token', 401);
}
};
// Role-based guard factory
export const requireRole = (...roles) => (req, res, next) => {
if (!roles.includes(req.user?.role))
throw new AppError('Forbidden', 403);
next();
};
Chapter 15
In 2026,Drizzle ORMhas become one of the most popular choices for PostgreSQL and MySQL in Node.js TypeScript projects due to its lightweight, SQL-first design and excellent type inference.Prismaremains widely used for its schema-driven approach and intuitive API. For MongoDB, Mongoose and the native MongoDB driver continue to be the dominant options.
JavaScript - Drizzle ORM (PostgreSQL)
import { drizzle } from 'drizzle-orm/node-postgres';
import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm/pg-core';
import { eq, and, like, desc } from 'drizzle-orm';
import pg from 'pg';
// Define schema
export const users = pgTable('users', {
id: serial('id').primaryKey(),
username: text('username').notNull().unique(),
email: text('email').notNull().unique(),
isActive: boolean('is_active').notNull().default(true),
createdAt: timestamp('created_at').defaultNow(),
});
// Connect
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle(pool, { schema: { users } });
// Select
const active = await db.select().from(users)
.where(and(eq(users.isActive, true), like(users.email, '%@acme.com')))
.orderBy(desc(users.createdAt))
.limit(10);
// Insert with returning
const [newUser] = await db.insert(users)
.values({ username: 'alice', email: 'alice@acme.com' })
.returning();
// Transaction
await db.transaction(async tx => {
const [u] = await tx.insert(users).values({...}).returning();
await tx.insert(profiles).values({ userId: u.id });
});
JavaScript - Cache-Aside
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
export const getCached = async (key, fetchFn, ttl = 300) => {
const hit = await redis.get(key);
if (hit) return JSON.parse(hit);
const fresh = await fetchFn();
await redis.setex(key, ttl, JSON.stringify(fresh));
return fresh;
};
// Usage
const user = await getCached(
`user:${userId}`,
() => db.findById(userId),
600
);
Chapter 16
GraphQLenables clients to request exactly the data they need, eliminating over-fetching and under-fetching. In 2026, the recommended Node.js GraphQL stack usesGraphQL Yogaas the server layer withPothosfor code-first, fully-typed schema building, andDataLoaderto batch and cache database lookups, solving the notorious N+1 query problem.
JavaScript - GraphQL Yoga
import { createSchema, createYoga } from 'graphql-yoga';
import { createServer } from 'node:http';
const schema = createSchema({
typeDefs: /* GraphQL */`
type User { id: ID! username: String! orders: [Order!]! }
type Order { id: ID! total: Float! status: String! }
type Query {
user(id: ID!): User
users(limit: Int = 20): [User!]!
}
type Mutation {
createUser(username: String!, email: String!): User!
}
`,
resolvers: {
Query: {
user: (_, { id }, ctx) => ctx.db.findById(id),
users: (_, args, ctx) => ctx.db.listUsers(args),
},
Mutation: {
createUser: (_, args, ctx) => ctx.db.createUser(args)
},
User: {
// DataLoader prevents N+1: batches all order lookups in one query
orders: ({ id }, _, ctx) => ctx.loaders.orders.load(id)
}
}
});
const yoga = createYoga({ schema, graphiql: true });
const srv = createServer(yoga);
srv.listen(4000, () => console.log('GraphQL at :4000/graphql'));
Chapter 17
JavaScript - Socket.IO Chat Server
import { createServer } from 'node:http';
import { Server } from 'socket.io';
import { app } from './app.js';
const httpSrv = createServer(app);
const io = new Server(httpSrv, { cors: { origin: process.env.CLIENT_URL } });
// Authenticate socket connections via JWT
io.use((socket, next) => {
try {
socket.user = verifyJWT(socket.handshake.auth.token);
next();
} catch { next(new Error('Unauthorized')); }
});
io.on('connection', socket => {
const uid = socket.user.sub;
socket.on('room:join', roomId => {
socket.join(roomId);
socket.to(roomId).emit('user:joined', { uid });
});
socket.on('msg:send', async ({ roomId, text }) => {
const msg = await db.saveMessage({ roomId, text, uid });
io.to(roomId).emit('msg:new', msg); // broadcast to all in room
});
socket.on('disconnect', () => console.log(`${uid} left`));
});
httpSrv.listen(3000);
Chapter 18
Node.js 22 ships abuilt-in test runner(node:test) that handles unit tests without installing anything extra. For project testing with mocking, coverage, and snapshots,Vitestis the leading choice in 2026 - it's fast, ESM-native, and Jest-compatible.Supertestremains the standard for HTTP integration tests.
JavaScript - Built-in test runner (node:test)
import { test, describe } from 'node:test';
import assert from 'node:assert/strict';
import { hashPwd, verifyPwd } from '../src/auth.js';
describe('Password hashing', () => {
test('hashes and verifies correctly', async () => {
const hash = await hashPwd('super-secret');
assert.notEqual(hash, 'super-secret');
assert.ok(await verifyPwd(hash, 'super-secret'));
});
test('rejects wrong password', async () => {
const hash = await hashPwd('correct');
assert.equal(await verifyPwd(hash, 'wrong'), false);
});
});
// Run: node --test
JavaScript - Supertest Integration
importrequestfrom'supertest';import{ app }from'../src/app.js';import{ describe, test }from'node:test';importassertfrom'node:assert/strict';describe('POST /api/v1/users', () => {test('201 with valid data',async() => {constres =awaitrequest(app).post('/api/v1/users') .send({ username:'tester', email:'t@example.com'}); assert.equal(res.status,201); assert.equal(res.body.data.username,'tester'); });test('400 for bad email',async() => {constres =awaitrequest(app).post('/api/v1/users') .send({ username:'x', email:'not-an-email'}); assert.equal(res.status,400); }); });
Chapter 19
🔐 OWASP Top 10 for Node.js APIs
Broken access control, cryptographic failures, injection, insecure design, and security misconfiguration top the list. Parameterised queries, Argon2id passwords, Helmet headers, strict input validation, and rate limiting address the vast majority of real-world vulnerabilities.
JavaScript - Security Setup
importhelmetfrom'helmet';importrateLimitfrom'express-rate-limit';importhppfrom'hpp';// Helmet sets: Content-Security-Policy, X-Frame-Options,
// X-Content-Type-Options, HSTS, Referrer-Policy, and moreapp.use(helmet({
contentSecurityPolicy: {
directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'"] }
}
}));// Tighter rate limit on auth endpointsapp.use('/api/auth',rateLimit({
windowMs:15601000,
max:10,
message: { error:'Too many auth attempts, try again later'}
}));// Prevent HTTP parameter pollutionapp.use(hpp());// Validate all required env vars at startup - fail fastconstREQUIRED = ['DATABASE_URL','JWT_SECRET','REDIS_URL'];for(constkofREQUIRED)if(!process.env[k])throw newError(Missing env:$\{k\});// Never log sensitive data - redact before logging
// Never store passwords in plain text - always Argon2id hash
// Always use HTTPS in production - terminate TLS at load balancer
// Enable PostgreSQL row-level security for multi-tenant apps
Chapter 20
JavaScript - Cluster Module
import cluster from 'node:cluster';
import os from 'node:os';
import { startServer } from './app.js';
if (cluster.isPrimary) {
const n = os.availableParallelism(); // Node 18+ API
console.log(`Forking ${n} workers (PID ${process.pid})`);
for (let i = 0; i < n; i++) cluster.fork();
cluster.on('exit', (w, code) => {
console.warn(`Worker ${w.process.pid} died (code ${code}). Restarting…`);
cluster.fork();
});
} else {
startServer();
}
// In production: PM2 handles clustering more robustly
// pm2 start server.js --instances max --name api -i max
Connection pooling- never create a new DB connection per request; use a pool (pg.Pool, mongoose connectionPool).Compression- enable gzip/brotli with thecompressionmiddleware.Caching- Redis for hot reads, HTTP cache headers for static assets.Streaming responses- stream large datasets instead of buffering.Database indexes- always index columns used in WHERE, JOIN, and ORDER BY.Avoid synchronous operations- never usefs.readFileSyncor CPU-heavy code on the main thread in a server context.
Chapter 21
Worker Threads allow Node.js to offload CPU-intensive work - image resizing, PDF generation, machine learning inference, heavy JSON transformation - to separate threads that run in parallel with the main event loop. Each worker has its own V8 instance and event loop. Communication happens via message passing (postMessage/on('message')) and shared memory (SharedArrayBuffer).
JavaScript - Worker Pool Pattern
import{ Worker }from'node:worker_threads';importosfrom'node:os';classWorkerPool { #workers = []; #queue = [];constructor(scriptPath, size = os.availableParallelism()) {for(leti =0; i < size; i++)this.#spawn(scriptPath); }
#spawn(scriptPath) {constw =newWorker(scriptPath); w.idle =true; w.on('message', result => { w.resolve(result); w.idle =true;this.#drain(); });this.#workers.push(w); }
#drain() {if(!this.#queue.length)return;constidle =this.#workers.find(w => w.idle);if(!idle)return;const{ data, resolve } =this.#queue.shift(); idle.idle =false; idle.resolve = resolve; idle.postMessage(data); }run(data) {return newPromise(resolve => {this.#queue.push({ data, resolve });this.#drain(); }); } }export constimagePool =newWorkerPool('./workers/imageResizer.js');// In route handler:app.post('/resize', upload.single('img'),async(req, res) => {constresult =awaitimagePool.run({ buffer: req.file.buffer, w:400, h:300}); res.json({ url: result.url }); });
Chapter 22
Microservices decompose a monolith into independently deployable units - each owning a specific business capability, its own database, and its own deployment lifecycle. Node.js is an excellent fit for microservices due to its fast startup, low memory footprint, and built-in support for async HTTP and event-driven communication.
JavaScript - BullMQ Job Queue
import { Queue, Worker } from 'bullmq';
import { Redis } from 'ioredis';
const conn = new Redis(process.env.REDIS_URL);
// Producer - add jobs from any service
export const emailQ = new Queue('emails', { connection: conn });
await emailQ.add('order-confirm', {
to: 'user@example.com', orderId: 'ord_9876'
}, {
attempts: 3,
backoff: { type: 'exponential', delay: 1000 }
});
// Consumer - email microservice
const worker = new Worker('emails', async job => {
console.log(`Processing ${job.id} (${job.name})`);
await sendEmail(job.data);
}, { connection: conn, concurrency: 5 });
worker.on('failed', (job, err) =>
console.error(`Job ${job?.id} failed: ${err.message}`)
);
Chapter 23
Dockerfile - Production Best Practice 2026
WORKDIR /app COPY package.json pnpm-lock.yaml ./ RUN corepack enable && pnpm install --frozen-lockfile# Stage 2: build (TypeScript)FROM node:22-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN pnpm build# Stage 3: minimal runtime imageFROM node:22-alpine AS runner WORKDIR /app ENV NODE_ENV=production# Non-root user for securityRUN addgroup -S app && adduser -S app -G app USER app
COPY --from=builder /app/dist ./dist COPY --from=deps /app/node_modules ./node_modules COPY package.json ./
EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s \ CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
YAML - docker-compose.yml
services:
api:
build: .
ports: ["3000:3000"]
env_file: .env
depends_on: [db, redis]
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: secret
POSTGRES_DB: mydb
volumes: [pgdata:/var/lib/postgresql/data]
redis:
image: redis:7-alpine
volumes: [redisdata:/data]
volumes:
pgdata:
redisdata:
Chapter 24
| Platform | Type | Best For | Cost | | --- | --- | --- | --- | | Railway / Render | Managed Containers | Startups, always-on APIs | Per hour | | Fly.io | Managed VMs | Global edge, WebSockets | Per VM | | Vercel / Netlify | Serverless Functions | Next.js, edge functions | Per invocation | | Google Cloud Run | Serverless Containers | Autoscaling, pay-per-request | Per request | | AWS ECS + Fargate | Managed Containers | Enterprise, complex infra | Per task/hour | | AWS Lambda | Serverless | Event-driven, infrequent tasks | Per invocation |
✅ Recommendation for 2026
Startups and small teams →Railway or Renderfor simplicity, orFly.iofor WebSocket-heavy apps with global users. Mid-size →Google Cloud Runfor effortless autoscaling with zero infrastructure management. Enterprise →AWS ECS + Fargateinside a private VPC with an Application Load Balancer.
JavaScript
const shutdown = async (signal) => {
console.log(`${signal} received - shutting down gracefully`);
server.close(async () => {
await db.end(); // close DB pool
await redis.quit(); // close Redis conn
console.log('Clean exit');
process.exit(0);
});
setTimeout(() => {
console.error('Forced exit after 10s');
process.exit(1);
}, 10_000);
};
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('uncaughtException', err => {
console.error('Uncaught:', err);
shutdown('uncaughtException');
});
Chapter 25
TypeScript adoption has reached near-total saturation in professional Node.js development. Node.js 22 now supports running.tsfiles directly via--experimental-strip-types(now stable in patch releases), dramatically reducing the friction of TypeScript adoption. For production, the standard build pipeline usestscto compile todist/, which is then run by Node.
TypeScript - Typed Express Controller
import { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
// Schema defines both runtime validation AND TypeScript type
const CreateUser = z.object({
username: z.string().min(3).max(50).regex(/^[a-z0-9_]+$/),
email: z.string().email(),
role: z.enum(['user', 'admin']).default('user'),
});
type CreateUserInput = z.infer<typeof CreateUser>;
// Typed request body
type TReq<T> = Request<{}, {}, T>;
export const createUser = async (
req: TReq<CreateUserInput>,
res: Response,
next: NextFunction
) => {
const user = await db.createUser(req.body); // req.body is fully typed
res.status(201).json({ data: user });
};
// Node 22: run TypeScript directly (no compilation!)
// node --experimental-strip-types server.ts
Chapter 26
AI-powered backends have become one of the most exciting use cases for Node.js in 2025-2026. The non-blocking architecture is perfectly suited for orchestrating calls to LLM APIs where most time is spent waiting on network responses. Node.js can handle thousands of simultaneous AI requests efficiently. Every major AI provider publishes a first-class npm package.
JavaScript - Anthropic Streaming + SSE
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
// POST /api/chat - stream response back to browser via SSE
app.post('/api/chat', async (req, res) => {
const { message, history = [] } = req.body;
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const stream = await client.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 1024,
stream: true,
messages: [...history, { role: 'user', content: message }]
});
for await (const chunk of stream) {
if (chunk.type === 'content_block_delta' && chunk.delta.type === 'text_delta') {
res.write(`data: ${JSON.stringify({ text: chunk.delta.text })}\n\n`);
}
}
res.write('data: [DONE]\n\n');
res.end();
});
JavaScript - Vector Search + LLM
import OpenAI from 'openai';
const ai = new OpenAI();
const embed = async text => {
const r = await ai.embeddings.create({ model: 'text-embedding-3-small', input: text });
return r.data[0].embedding;
};
const vectorSearch = async (query, k = 5) => {
const vec = await embed(query);
// pgvector cosine similarity search
return db.query(`
SELECT content, 1 - (embedding <=> $1::vector) AS sim
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT $2
`, [`[${vec.join(',')}]`, k]);
};
app.post('/api/ask', async (req, res) => {
const docs = await vectorSearch(req.body.question);
const context = docs.rows.map(d => d.content).join('\n---\n');
const answer = await ai.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: `Context:\n${context}` },
{ role: 'user', content: req.body.question }
]
});
res.json({ answer: answer.choices[0].message.content });
});
Chapter 27
The event loop is a single-threaded mechanism that allows Node.js to perform non-blocking I/O despite running on one thread. It processes phases sequentially - timers, I/O callbacks, poll, check, close - and drains the microtask queue (nextTick, Promises) between each phase. It's important because it enables high concurrency: rather than spawning a thread per connection (which wastes memory), Node.js queues callbacks for completed I/O and processes them efficiently on one thread.
process.nextTick()fires at the end of the current operation, before any I/O events, in the microtask queue - it runs before all other async callbacks.setImmediate()fires in the check phase of the event loop, after I/O events are processed. Excessive use ofprocess.nextTick()can starve I/O by never letting the loop advance; usesetImmediate()for deferred work that should not delay I/O.
Common causes: accumulated event listeners not removed (use.once()orremoveListener()), closures holding references to large objects, unbounded in-memory caches without eviction, and global variables accumulating state. Tools:node --inspectwith Chrome DevTools heap snapshots,clinic.jsfor flamegraph profiling,node:v8writeHeapSnapshot API, and proper coding patterns like weak references (WeakMap,WeakRef) for caches.
Use streams whenever: the data might not fit in memory (large file exports, multi-GB CSV processing), the data arrives incrementally (HTTP request bodies, network sockets), or you want to start processing early without waiting for the full dataset. Streams provide constant memory usage regardless of data size, making them essential for high-throughput Node.js services.
CommonJS: synchronousrequire()/module.exports, runs in all Node.js versions, no file extension required. ESM: staticimport/export, asynchronous loading, supports tree-shaking, requires file extensions in import paths, supports top-level await, and is the ECMA standard. In 2026, new projects should use ESM. ESM can import CJS but not vice versa without dynamicimport().
Node.js uses one CPU core by default. Theclustermodule forks multiple child processes that share the same server port. The primary process distributes incoming connections across workers (round-robin on Linux). This multiplies throughput proportional to available CPU cores. In production, PM2's cluster mode (--instances max) is preferred for its process management, monitoring, and zero-downtime restart capabilities.
Worker Threads run JavaScript in parallel threads with their own V8 instance and event loop. Unlike clustering (separate processes), workers share the same address space and can communicate viaSharedArrayBuffer. Use them when you need CPU-heavy work that would block the event loop: image processing, PDF generation, complex JSON transformation, ML inference, cryptography. Don't use them for I/O-bound work - the event loop handles that efficiently without threads.
In Express 5, async route handlers that throw (or return rejected promises) automatically pass the error to the error-handling middleware - notry/catch + next(err)wrapper required. In Express 4, you must either wrap async handlers with a helper or explicitly catch and callnext(err). The global error handler has four parameters(err, req, res, next)and must be registered after all routes.
Chapter 28
Complete Quick Reference
// ═══ MODULES (ESM) ═══════════════════════════════════════importxfrom'./x.js';// default (ext required)import{ a, b }from'./m.js';// namedimport*asmfrom'./m.js';// namespaceconst{ fn } =await import('./lazy.js');// dynamicexport constfn = () => {};// named exportexport defaultclass X {};// default export// ═══ ASYNC PATTERNS ══════════════════════════════════════constx =awaitsomePromise();const[a, b] =awaitPromise.all([p1(), p2()]);constresults =awaitPromise.allSettled([p1(), p2()]);constfirst =awaitPromise.race([p1(),timeout(3000)]);awaitPromise.all(items.map(fn));// parallel map (fast)// ═══ FILESYSTEM (promises) ══════════════════════════════consts =awaitreadFile('./f.txt','utf8');awaitwriteFile('./out.txt', data);awaitmkdir('./dir', { recursive:true});constls =awaitreaddir('./src');awaitrm('./tmp', { recursive:true});const{ size } =awaitstat('./pkg.json');// ═══ FETCH (built-in since Node 18) ══════════════════════constr =awaitfetch('https://api.example.com/data');constjson =awaitr.json();constr2 =awaitfetch(url, { method:'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(payload) });// ═══ PROCESS ══════════════════════════════════════════════process.env.NODE_ENV;// 'production' | 'development'process.env.PORT ??'3000';// env with fallbackprocess.exit(0);// clean exitprocess.on('SIGTERM', graceful);// shutdown signalprocess.on('uncaughtException', h);// last resortprocess.on('unhandledRejection', h);// unhandled promises// ═══ CRYPTO ═══════════════════════════════════════════════crypto.randomUUID();// UUID v4crypto.randomBytes(32).toString('hex'); crypto.createHash('sha256').update(d).digest('hex');// ═══ TIMERS ═══════════════════════════════════════════════import{ setTimeout as sleep }from'node:timers/promises';awaitsleep(1000);// sleep 1 second (clean)// ═══ EVENTS ═══════════════════════════════════════════════import{ EventEmitter }from'node:events';classBusextendsEventEmitter {}constbus =newBus(); bus.on('order:created', handler); bus.emit('order:created', { id:42}); bus.once('init', handler);// fires only once// ═══ STREAMS ══════════════════════════════════════════════awaitpipeline(readable, transform, writable);consts = Readable.from(asyncGenerator());// ═══ SECURITY ═════════════════════════════════════════════consth =awaitargon2.hash(pwd, { type: argon2.argon2id });constok=awaitargon2.verify(h, pwd);sign({ sub: uid, role }, secret, { expiresIn:'15m'});verify(token, secret);// throws if invalid/expired
Chapter 29
Node.js in 2026 is a mature, battle-tested platform that has answered every major criticism levelled against it over the years. Type safety is addressed by native TypeScript support. Callback hell was solved by async/await. CPU-bound work is handled by Worker Threads. Poor startup time is being addressed by startup snapshots and V8 code caching. Security is improved by the built-in permission model. The ecosystem continues to grow and the community remains the largest in server-side programming.
Looking toward 2027, several trends will shape the next chapter of Node.js.Native TypeScript executionwill become fully stable and recommended for production, eliminating the build step entirely.WebAssembly GCintegration will mature, enabling near-native performance for computation-heavy modules. Theedge computingwave - running JavaScript at CDN edge nodes worldwide - will continue to grow, and Node.js compatibility with edge runtimes (via the WinterCG specification) is improving with every release.
AI-native applicationsare the dominant new Node.js use case in 2026 and will intensify through 2027. Streaming LLM responses, orchestrating multi-step AI agents, managing conversation context, implementing RAG pipelines, and exposing AI capabilities as APIs - all of these are patterns where Node.js's non-blocking I/O and rich ecosystem give it a compelling advantage over alternatives.
For developers starting or deepening their Node.js journey in 2026, the fundamentals have never mattered more. Understanding the event loop enables you to write correct, efficient code. Mastering async/await and Promise combinators makes parallel programming intuitive. TypeScript from day one eliminates an enormous class of runtime bugs. Docker and cloud deployment skills are as important as the code itself. And increasingly, integrating AI capabilities is becoming a baseline expectation rather than a differentiator.
✅ Your Node.js Learning Roadmap 2026-2027
Node.js
JavaScript
TypeScript
Express.js
REST API
GraphQL
WebSockets
Streams
Microservices
Docker
Cloud
AI Integration
2026
2027
Backend
JavaScript Complete Guide
45 min - Beginner
Python for Beginners 15 Day Plan
40 min - Beginner
Java + DSA Interview Prep 2026-2027
35 min - Intermediate
Top 50 Java Interview Questions
25 min - Intermediate
Machine Learning Complete Guide
40 min - Intermediate
Artificial Intelligence Complete Guide
35 min - Intermediate