Production-ready Node.js patterns — testing, file system, workers, streams, crypto, and operational concerns.
Available from Node 18 (experimental) and Node 20+ (stable).
// test/user.test.mjs
import { describe, it, before, after, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import { createUser, getUser, deleteUser } from '../src/user.js';
describe('User module', () => {
let userId;
before(async () => {
// Setup — runs once before all tests in this describe block
await db.connect();
});
after(async () => {
// Teardown — runs once after all tests
await db.disconnect();
});
beforeEach(async () => {
// Runs before each test
const user = await createUser({ name: 'Alice', email: 'alice@example.com' });
userId = user.id;
});
afterEach(async () => {
// Runs after each test
await deleteUser(userId).catch(() => {}); // ignore if already deleted
});
it('creates a user', async () => {
const user = await getUser(userId);
assert.equal(user.name, 'Alice');
assert.equal(user.email, 'alice@example.com');
assert.ok(user.id, 'should have an id');
});
it('throws on missing user', async () => {
await assert.rejects(
() => getUser('nonexistent-id'),
{ name: 'NotFoundError' }
);
});
});
# Run all test files
node --test
# Specific files or glob
node --test test/**/*.test.mjs
# Watch mode (re-runs on file change)
node --test --watch
# With coverage (experimental)
node --test --experimental-test-coverage
# Filter by test name
node --test --test-name-pattern="User module"
# Concurrency (default: os.availableParallelism() - 1)
node --test --test-concurrency=4
# Reporter (spec, tap, dot, junit)
node --test --test-reporter=spec
node --test --test-reporter=junit --test-reporter-destination=results.xml
import { describe, it, mock } from 'node:test';
import assert from 'node:assert/strict';
// Mock a function
const fn = mock.fn((x) => x * 2);
fn(5);
fn(10);
assert.equal(fn.mock.calls.length, 2);
assert.deepEqual(fn.mock.calls[0].arguments, [5]);
assert.equal(fn.mock.calls[0].result, 10);
// Reset mock state
fn.mock.resetCalls();
// Restore mocked methods
fn.mock.restore();
// Mock module methods
import { describe, it, mock, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import * as fs from 'node:fs/promises';
describe('config loader', () => {
afterEach(() => mock.restoreAll()); // important — restore after each test
it('loads config from file', async () => {
// Replace fs.readFile with a mock
mock.method(fs, 'readFile', async () => '{"port": 3000}');
const config = await loadConfig('./config.json');
assert.equal(config.port, 3000);
});
it('uses default when file missing', async () => {
mock.method(fs, 'readFile', async () => {
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
});
const config = await loadConfig('./config.json');
assert.deepEqual(config, { port: 8080 }); // default
});
});
import { describe, it, mock } from 'node:test';
import assert from 'node:assert/strict';
describe('debounce', () => {
it('delays execution', async () => {
// Take control of timers
mock.timers.enable(['setTimeout', 'setInterval']);
const fn = mock.fn();
const debounced = debounce(fn, 1000);
debounced();
debounced();
debounced();
assert.equal(fn.mock.calls.length, 0); // not called yet
// Advance time by 1000ms
mock.timers.tick(1000);
assert.equal(fn.mock.calls.length, 1); // called once (debounced)
mock.timers.reset();
});
});
import { describe, it } from 'node:test';
describe('serializer', () => {
it('formats output correctly', (t) => {
const output = serialize({ name: 'Alice', scores: [1, 2, 3] });
t.assert.snapshot(output);
// First run: creates .snap file
// Subsequent runs: compares against snapshot
});
});
// Update snapshots:
// node --test --test-update-snapshots
import {
readFile, writeFile, appendFile,
readdir, mkdir, rm, rename, copyFile,
stat, access, watch,
open, // FileHandle for streaming
} from 'node:fs/promises';
import { constants } from 'node:fs';
// Read file
const content = await readFile('./config.json', 'utf8');
const data = JSON.parse(content);
// Write file (creates or overwrites)
await writeFile('./output.json', JSON.stringify(data, null, 2), 'utf8');
// Append
await appendFile('./log.txt', `${new Date().toISOString()} — event\n`);
// Check if file exists
try {
await access('./config.json', constants.F_OK);
console.log('File exists');
} catch {
console.log('File does not exist');
}
// Stat — file metadata
const stats = await stat('./config.json');
stats.isFile(); // true
stats.isDirectory(); // false
stats.size; // bytes
stats.mtime; // last modified Date
// Read directory
const entries = await readdir('./src', { withFileTypes: true });
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
const files = entries.filter(e => e.isFile()).map(e => e.name);
// Recursive readdir (Node 18.17+)
const allFiles = await readdir('./src', { recursive: true });
// Create directory (recursive creates intermediate dirs)
await mkdir('./dist/assets/images', { recursive: true });
// Delete — rm replaces deprecated rmdir
await rm('./dist', { recursive: true, force: true }); // rm -rf
// Rename / move
await rename('./old-name.js', './new-name.js');
// Copy
await copyFile('./src/file.js', './dist/file.js');
// Native glob — no glob package needed in Node 22+
import { glob } from 'node:fs/promises';
const tsFiles = await Array.fromAsync(glob('**/*.ts', { cwd: './src' }));
const testFiles = await Array.fromAsync(glob('**/*.test.{js,mjs,ts}'));
// Watch a file or directory for changes
const watcher = watch('./src', { recursive: true });
for await (const event of watcher) {
console.log(event.eventType, event.filename);
// event.eventType: 'rename' | 'change'
// event.filename: relative path from watch target
}
// Stop watching
watcher.close();
// OR: use AbortSignal
const ac = new AbortController();
const watcher2 = watch('./config.json', { signal: ac.signal });
for await (const event of watcher2) {
if (shouldStop) ac.abort();
await reloadConfig();
}
import { open } from 'node:fs/promises';
// FileHandle for large files — streaming read
async function processLargeFile(path) {
const handle = await open(path, 'r');
try {
const stream = handle.createReadStream({ encoding: 'utf8' });
for await (const chunk of stream) {
await processChunk(chunk);
}
} finally {
await handle.close(); // always close
}
}
// With 'using' (Node 22+ / ES2025)
async function processWithUsing(path) {
await using handle = await open(path, 'r'); // auto-closes
for await (const chunk of handle.createReadStream()) {
await processChunk(chunk);
}
}
// worker_threads — true parallelism, shared memory
import { Worker, isMainThread, parentPort, workerData,
receiveMessageOnPort, MessageChannel } from 'node:worker_threads';
import { cpus } from 'node:os';
// main.mjs
import { Worker } from 'node:worker_threads';
function runWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker('./worker.mjs', { workerData: data });
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) reject(new Error(`Worker exited with code ${code}`));
});
});
}
// Parallel CPU work across all cores
const cpuCount = cpus().length;
const chunks = splitData(data, cpuCount);
const results = await Promise.all(chunks.map(chunk => runWorker(chunk)));
const final = mergeResults(results);
// worker.mjs
import { parentPort, workerData } from 'node:worker_threads';
// workerData is a deep clone of what was passed to Worker constructor
const result = heavyComputation(workerData);
// Send result back to main thread
parentPort.postMessage(result);
// Reusable worker pool — avoid create/destroy overhead
class WorkerPool {
#workers = [];
#queue = [];
#size;
constructor(workerPath, size = cpus().length) {
this.#size = size;
this.#workers = Array.from({ length: size }, () => ({
worker: new Worker(workerPath),
busy: false,
}));
for (const entry of this.#workers) {
entry.worker.on('message', (result) => {
entry.busy = false;
entry.resolve(result);
this.#processQueue();
});
}
}
run(data) {
return new Promise((resolve, reject) => {
this.#queue.push({ data, resolve, reject });
this.#processQueue();
});
}
#processQueue() {
if (!this.#queue.length) return;
const idle = this.#workers.find(w => !w.busy);
if (!idle) return;
const { data, resolve, reject } = this.#queue.shift();
idle.busy = true;
idle.resolve = resolve;
idle.reject = reject;
idle.worker.postMessage(data);
}
async terminate() {
await Promise.all(this.#workers.map(({ worker }) => worker.terminate()));
}
}
const pool = new WorkerPool('./image-processor.mjs', 4);
const thumbnails = await Promise.all(
images.map(img => pool.run({ image: img, width: 200 }))
);
await pool.terminate();
// main.mjs
import { Worker } from 'node:worker_threads';
const shared = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 1024);
const counter = new Int32Array(shared);
const workers = Array.from({ length: 4 }, () =>
new Worker('./counter-worker.mjs', {
workerData: { shared }, // no transfer needed — it's shared
})
);
await Promise.all(workers.map(w => new Promise(r => w.on('exit', r))));
console.log('Final count:', Atomics.load(counter, 0));
// counter-worker.mjs
import { workerData } from 'node:worker_threads';
const counter = new Int32Array(workerData.shared);
for (let i = 0; i < 10_000; i++) {
Atomics.add(counter, 0, 1); // atomic — no race condition
}
import cluster from 'node:cluster';
import { cpus } from 'node:os';
import { createServer } from 'node:http';
if (cluster.isPrimary) {
// Fork one worker per CPU
const numCPUs = cpus().length;
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died (${signal || code})`);
cluster.fork(); // auto-restart
});
// Graceful restart — zero-downtime deploy
process.on('SIGUSR2', () => {
const workers = Object.values(cluster.workers);
let i = 0;
function restartNext() {
if (i >= workers.length) return;
const worker = workers[i++];
worker.once('exit', () => {
cluster.fork().once('listening', restartNext);
});
worker.kill('SIGTERM');
}
restartNext();
});
} else {
// Worker process
createServer((req, res) => {
res.writeHead(200);
res.end(`Worker ${process.pid}: hello\n`);
}).listen(3000);
// Graceful shutdown signal from primary
process.on('SIGTERM', () => {
server.close(() => process.exit(0));
});
}
import { pipeline, Transform, Readable, Writable } from 'node:stream';
import { promisify } from 'node:util';
import { createReadStream, createWriteStream } from 'node:fs';
import { createGzip, createGunzip } from 'node:zlib';
const pipelineAsync = promisify(pipeline);
// OR: import { pipeline } from 'node:stream/promises';
// Compress a file
await pipelineAsync(
createReadStream('./large-file.txt'),
createGzip(),
createWriteStream('./large-file.txt.gz'),
);
// Custom Transform stream
class CSVParser extends Transform {
#buffer = '';
#headers = null;
constructor() {
super({ readableObjectMode: true }); // output objects
}
_transform(chunk, encoding, callback) {
this.#buffer += chunk.toString();
const lines = this.#buffer.split('\n');
this.#buffer = lines.pop(); // keep incomplete last line
for (const line of lines) {
if (!this.#headers) {
this.#headers = line.split(',').map(h => h.trim());
} else {
const values = line.split(',');
const record = Object.fromEntries(
this.#headers.map((h, i) => [h, values[i]?.trim()])
);
this.push(record);
}
}
callback();
}
_flush(callback) {
if (this.#buffer.trim() && this.#headers) {
const values = this.#buffer.split(',');
this.push(Object.fromEntries(
this.#headers.map((h, i) => [h, values[i]?.trim()])
));
}
callback();
}
}
// Process a large CSV without loading it all into memory
await pipeline(
createReadStream('./data.csv'),
new CSVParser(),
new Writable({
objectMode: true,
async write(record, encoding, callback) {
try {
await db.insert('records', record);
callback();
} catch (err) {
callback(err);
}
},
}),
);
import { Readable } from 'node:stream';
// Convert Node stream to Web ReadableStream
const nodeReadable = createReadStream('./file.txt');
const webStream = Readable.toWeb(nodeReadable);
// Convert Web ReadableStream to Node stream
const nodeFromWeb = Readable.fromWeb(webStream);
// Use web streams with fetch response body
const response = await fetch('https://api.example.com/large');
for await (const chunk of response.body) { // response.body is ReadableStream
await processChunk(chunk);
}
import { createServer } from 'node:http';
const server = createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
if (url.pathname === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', uptime: process.uptime() }));
return;
}
if (req.method === 'POST' && url.pathname === '/api/data') {
// Read request body
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({ received: body }));
return;
}
res.writeHead(404);
res.end('Not found');
});
server.listen(3000, () => console.log('Server on :3000'));
// undici is bundled with Node 18+ — faster than node-fetch
import { fetch, request, stream, pipeline } from 'undici';
// Standard fetch (same as global fetch in Node 18+)
const res = await fetch('https://api.example.com/users');
const users = await res.json();
// Low-level request with connection pooling
const { statusCode, headers, body } = await request('https://api.example.com/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' }),
});
const data = await body.json();
// Stream response directly to file
import { createWriteStream } from 'node:fs';
await stream('https://example.com/large-file.zip',
{ method: 'GET' },
() => createWriteStream('./download.zip')
);
import {
randomUUID, randomBytes, randomInt,
createHash, createHmac,
createCipheriv, createDecipheriv,
scrypt, scryptSync,
timingSafeEqual,
generateKeyPair, createSign, createVerify,
} from 'node:crypto';
import { promisify } from 'node:util';
const scryptAsync = promisify(scrypt);
// Unique IDs
const id = randomUUID(); // cryptographically random UUID v4
const token = randomBytes(32).toString('hex'); // 64-char hex token
const otp = randomInt(100_000, 999_999); // 6-digit OTP
// Hashing
const hash = createHash('sha256').update('content').digest('hex');
// HMAC
const hmac = createHmac('sha256', 'secret-key').update('message').digest('hex');
// Password hashing with scrypt
async function hashPassword(password) {
const salt = randomBytes(16);
const hash = await scryptAsync(password, salt, 64);
return `${salt.toString('hex')}:${hash.toString('hex')}`;
}
async function verifyPassword(password, stored) {
const [saltHex, hashHex] = stored.split(':');
const salt = Buffer.from(saltHex, 'hex');
const hash = await scryptAsync(password, salt, 64);
// timingSafeEqual prevents timing attacks
return timingSafeEqual(hash, Buffer.from(hashHex, 'hex'));
}
// AES-256-GCM encryption
function encrypt(plaintext, keyHex) {
const key = Buffer.from(keyHex, 'hex'); // 32 bytes for AES-256
const iv = randomBytes(16);
const cipher = createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([
cipher.update(plaintext, 'utf8'),
cipher.final(),
]);
const authTag = cipher.getAuthTag();
return {
iv: iv.toString('hex'),
encrypted: encrypted.toString('hex'),
authTag: authTag.toString('hex'),
};
}
function decrypt({ iv, encrypted, authTag }, keyHex) {
const key = Buffer.from(keyHex, 'hex');
const decipher = createDecipheriv(
'aes-256-gcm',
key,
Buffer.from(iv, 'hex')
);
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
return Buffer.concat([
decipher.update(Buffer.from(encrypted, 'hex')),
decipher.final(),
]).toString('utf8');
}
// RSA signing
const { privateKey, publicKey } = await promisify(generateKeyPair)('rsa', {
modulusLength: 2048,
});
const sign = createSign('SHA256');
sign.update('message to sign');
const signature = sign.sign(privateKey, 'hex');
const verify = createVerify('SHA256');
verify.update('message to sign');
const isValid = verify.verify(publicKey, signature, 'hex');
import diagnostics from 'node:diagnostics_channel';
// Publisher — library code
const ch = diagnostics.channel('mylib:db:query');
async function query(sql, params) {
if (ch.hasSubscribers) {
ch.publish({ sql, params, start: performance.now() });
}
const result = await db.execute(sql, params);
if (ch.hasSubscribers) {
ch.publish({ sql, params, duration: performance.now() - start, rows: result.rowCount });
}
return result;
}
// Subscriber — monitoring/APM code
diagnostics.channel('mylib:db:query').subscribe((data) => {
metrics.histogram('db.query.duration', data.duration, { sql: data.sql });
});
// Subscribe to undici (built-in HTTP client) events
diagnostics.channel('undici:request:create').subscribe((data) => {
console.log('HTTP request:', data.request.method, data.request.origin + data.request.path);
});
import { performance, PerformanceObserver } from 'node:perf_hooks';
// Mark + measure
performance.mark('start');
await doWork();
performance.mark('end');
performance.measure('doWork', 'start', 'end');
const entries = performance.getEntriesByName('doWork');
console.log(`Duration: ${entries[0].duration}ms`);
// Observe all measures
const obs = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.duration.toFixed(2)}ms`);
}
});
obs.observe({ entryTypes: ['measure'] });
// timerify — auto-measure function calls
function myFunction() { /* ... */ }
const timerified = performance.timerify(myFunction);
obs.observe({ entryTypes: ['function'] });
timerified(); // automatically measured
// register-hooks.mjs — entry point
import { register } from 'node:module';
register('./typescript-loader.mjs', import.meta.url);
// typescript-loader.mjs
import { transform } from 'esbuild';
export async function load(url, context, nextLoad) {
if (url.endsWith('.ts') || url.endsWith('.tsx')) {
const { source } = await nextLoad(url, { ...context, format: 'module' });
const { code } = await transform(source.toString(), {
loader: url.endsWith('.tsx') ? 'tsx' : 'ts',
format: 'esm',
target: 'node18',
});
return { format: 'module', shortCircuit: true, source: code };
}
return nextLoad(url, context);
}
# Use the loader
node --import ./register-hooks.mjs ./src/main.ts
// Experimental permission model — opt-in security sandboxing
// Deny all by default, explicitly allow what you need
# Allow reading only from specific directories
node --experimental-permission \
--allow-fs-read=/app/config \
--allow-fs-write=/app/logs \
--allow-net=api.example.com:443 \
--allow-child-process \
server.mjs
# Check permissions at runtime
process.permission.has('fs.read', '/app/config/db.json'); // true
process.permission.has('fs.write', '/etc/passwd'); // false
# Deny all network access
node --experimental-permission --allow-fs-read=. --allow-fs-write=./dist bundler.mjs
// Root package.json
{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*", "apps/*"]
}
# Install deps for all packages
npm install
# Run script in specific package
npm run build --workspace=packages/my-lib
# Run script in all packages
npm run test --workspaces
# Add dep to a specific workspace
npm install zod --workspace=packages/my-lib
# Enable corepack (built into Node 16.9+)
corepack enable
# Use specific pnpm version
corepack use pnpm@9.0.0
# Adds "packageManager": "pnpm@9.0.0" to package.json
# Corepack downloads and uses exact version — no global installs
{
"name": "my-package",
"version": "1.0.0",
"engines": {
"node": ">=18.17.0",
"npm": ">=9.0.0"
},
"packageManager": "pnpm@9.0.0",
"overrides": {
"vulnerable-package": ">=2.1.0"
}
}
// server.mjs — production-ready graceful shutdown
import { createServer } from 'node:http';
const server = createServer(handler);
server.listen(3000);
// Track active connections for graceful drain
let isShuttingDown = false;
const connections = new Set();
server.on('connection', (socket) => {
connections.add(socket);
socket.on('close', () => connections.delete(socket));
});
async function shutdown(signal) {
if (isShuttingDown) return;
isShuttingDown = true;
console.log(`Received ${signal}. Starting graceful shutdown...`);
// Stop accepting new connections
server.close();
// Set response header to signal clients to disconnect
// (Connection: close is set automatically during close())
// Wait for in-flight requests (max 30s)
const forceShutdown = setTimeout(() => {
console.error('Forcing shutdown after timeout');
for (const socket of connections) socket.destroy();
process.exit(1);
}, 30_000);
// Wait for all connections to close naturally
await new Promise(resolve => server.on('close', resolve));
clearTimeout(forceShutdown);
// Cleanup other resources
await db.end();
await redis.quit();
console.log('Graceful shutdown complete');
process.exit(0);
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
// Unhandled errors — always exit
process.on('unhandledRejection', (reason) => {
console.error('Unhandled rejection:', reason);
process.exit(1);
});
process.on('uncaughtException', (err) => {
console.error('Uncaught exception:', err);
process.exit(1);
});
# No dotenv package needed
node --env-file=.env server.mjs
node --env-file=.env --env-file=.env.local server.mjs # multiple files, later wins
// Validate required env vars at startup
const required = ['DATABASE_URL', 'API_KEY', 'JWT_SECRET'];
const missing = required.filter(key => !process.env[key]);
if (missing.length) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
// Standard health check pattern for load balancers
import { createServer } from 'node:http';
const healthServer = createServer(async (req, res) => {
if (req.url !== '/health') {
res.writeHead(404);
res.end();
return;
}
try {
// Check dependencies
await Promise.all([
db.query('SELECT 1'),
redis.ping(),
]);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'healthy',
uptime: process.uptime(),
memory: process.memoryUsage(),
version: process.env.npm_package_version,
}));
} catch (err) {
res.writeHead(503, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'unhealthy', error: err.message }));
}
});
// Bind to different port from main app (accessible internally, not publicly)
healthServer.listen(3001);