---
name: nodejs-best-practices
description: "Use when designing, implementing, auditing, and hardening nodejs best practices server logic, APIs, background jobs, and error boundaries."
version: 6.0.0
last-updated: 2026-09-29
skills:
  - backend-security-expert
  - api-patterns
  - clean-code
tools: Read, Grep, Glob, Bash, Edit, Write
scripts-binding:
  - .agent/scripts/lint_runner.js
  - .agent/scripts/verify_all.js
inputs:
  task: "string"
  target_file: "string"
outputs:
  result: "string"
  verification_status: "boolean"
trigger:
  - "nodejs"
  - "best"
  - "practices"
---

# Node.js Best Practices — Node 22+ Production Mastery

## Mandatory Pre-Flight Context Inspection
Before reading, generating, or refactoring code in the `nodejs-best-practices` domain, inspect these 5 critical parameters:
1. **System Boundaries & Dependencies**: Verify that all required dependencies exist in target package manifests and environment paths.
2. **Runtime Context & Platform Invariants**: Confirm target platform constraints (Node.js, Browser, Mobile OS, Edge runtime) before applying APIs.
3. **Execution Guardrails**: Identify potential side-effects, state mutations, and unhandled asynchronous exceptions.
4. **Validation & Type Contracts**: Validate input data schemas and strict type constraints across all module interfaces.
5. **Observability & Proof of Execution**: Ensure execution produces tangible verification signals (terminal output, tests, metrics).


## Activation Boundaries
- **Activate when:** Use when designing, implementing, auditing, and hardening nodejs best practices server logic, APIs, background jobs, and error boundaries.
- **DO NOT activate when:** The task falls outside the `nodejs-best-practices` domain or is managed by a different dedicated specialist agent.


## 🔁 Multi-Pass Execution Protocol

| Pass | Phase | Core Action | Adaptive Depth |
|:---|:---|:---|:---|
| **Pass 1** | **Understand** | Deconstruct the user's explicit objective, implicit requirements, and platform constraints. | Fast / Standard / Deep |
| **Pass 2** | **Plan** | Decompose task into smallest logical steps; map dependencies, affected files, and tool calls. | Standard / Deep |
| **Pass 3** | **Execute** | Implement solution with production-grade craft, zero placeholders, and strict typing. | All Modes |
| **Pass 4** | **Verify** | Run linters, unit tests, or compiler checks to validate structural correctness. | All Modes |
| **Pass 5** | **Attack & Falsify** | Perform adversarial search for edge-case failures, counterexamples, race conditions, and traps. | Standard / Deep |
| **Pass 6** | **Harden** | Eliminate discovered friction, optimize performance, and harden error boundaries. | Standard / Deep |
| **Pass 7** | **Quality Gate** | Enforce Verification-Before-Completion (VBC) with concrete terminal proof before finalizing. | All Modes |


---

## 🛠️ Technical Architecture & Reference Recipes

## 2026 Node.js 22+ Performance & Runtime Invariants

1. **`node:sqlite` Built-in Database**: In Node 22+, use `node:sqlite` for embedded storage without requiring compiled native C++ bindings:
   ```ts
   import { DatabaseSync } from 'node:sqlite';
   const db = new DatabaseSync(':memory:');
   ```
2. **`import.meta.dirname` & `import.meta.filename`**: Native in Node 20.11+ and 22+:
   ```ts
   import { join } from 'node:path';
   const configPath = join(import.meta.dirname, 'config.json');
   ```
3. **Safe Async Stream Pipelining**: Always use `pipeline` from `node:stream/promises` to automatically destroy streams on error and prevent memory leaks.
4. **Native Test Runner**: Use `node:test` and `node:assert/strict` for zero-dependency high-speed test suites.

## Hallucination Traps (Read First)

- ❌ `import fs from "fs"` → ✅ `import fs from "node:fs"` (always use `node:` prefix)
- ❌ `path.dirname(fileURLToPath(import.meta.url))` → ✅ Node 22+: `import.meta.dirname`
- ❌ Installing `better-sqlite3` for basic SQLite → ✅ Use native `import { DatabaseSync } from 'node:sqlite'`
- ❌ `fs.readFileSync()` inside request handlers → ✅ `await fs.readFile()` from `node:fs/promises`
- ❌ Unhandled promise rejections without crashing → ✅ In Node 22+, always handle or exit cleanly

---

## ES Modules (Mandatory)

```json
// package.json — ALWAYS set type to module
{
  "type": "module",
  "engines": { "node": ">=22" }
}
```

```typescript
// ✅ ESM imports (modern Node.js)
import { readFile, writeFile } from 'node:fs/promises';
import { join, resolve } from 'node:path';
import { createServer } from 'node:http';
import { EventEmitter } from 'node:events';

// ❌ HALLUCINATION TRAP: Use node: protocol prefix for built-in modules
// ❌ import fs from "fs";       ← ambiguous (could be npm package)
// ✅ import fs from "node:fs";  ← explicitly a Node.js built-in

// ❌ HALLUCINATION TRAP: Do NOT use require() in ESM projects
// ❌ const fs = require("fs");  ← CommonJS (legacy)
// ✅ import fs from "node:fs/promises";  ← ESM

// Dynamic imports (for conditional loading)
const module = await import('./heavy-module.js');
```

---

## Framework Selection

```
┌────────────────────────────────────────────────────────────┐
│                  When to Use What                           │
├────────────────────────────────────────────────────────────┤
│ Express     │ Legacy projects, extensive middleware ecosystem│
│ Fastify     │ Performance-critical APIs, schema validation  │
│ Hono        │ Edge/serverless, multi-runtime (Node/Deno/Bun)│
│ tRPC        │ Full-stack TypeScript (Next.js + React Query) │
│ Raw http    │ Learning, minimal proxies, health checks      │
└────────────────────────────────────────────────────────────┘
```

### Fastify (Recommended)

```typescript
import Fastify from 'fastify';
import { z } from 'zod';

const app = Fastify({
  logger: {
    level: process.env.LOG_LEVEL ?? 'info',
    transport: process.env.NODE_ENV === 'development' ? { target: 'pino-pretty' } : undefined,
  },
});

// Schema validation with Zod → Fastify schema
const CreateUserSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  role: z.enum(['admin', 'user']).default('user'),
});

type CreateUserBody = z.infer<typeof CreateUserSchema>;

app.post<{ Body: CreateUserBody }>(
  '/users',
  {
    schema: {
      body: {
        type: 'object',
        required: ['name', 'email'],
        properties: {
          name: { type: 'string', minLength: 2 },
          email: { type: 'string', format: 'email' },
          role: { type: 'string', enum: ['admin', 'user'] },
        },
      },
    },
  },
  async (request, reply) => {
    const validated = CreateUserSchema.parse(request.body);
    const user = await createUser(validated);
    return reply.status(201).send(user);
  },
);

// Graceful shutdown
const start = async () => {
  try {
    await app.listen({ port: 3000, host: '0.0.0.0' });
  } catch (err) {
    app.log.error(err);
    process.exit(1);
  }
};

start();
```

### Hono (Edge-First)

```typescript
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';

const app = new Hono();

app.use('*', logger());
app.use('*', cors({ origin: 'https://myapp.com' }));

const createUserSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
});

app.post('/users', zValidator('json', createUserSchema), async c => {
  const body = c.req.valid('json');
  const user = await createUser(body);
  return c.json(user, 201);
});

app.get('/health', c => c.json({ status: 'ok' }));

export default app; // works in Node, Deno, Bun, Cloudflare Workers
```

---

## Error Handling

### Global Error Handlers

```typescript
// ✅ MANDATORY: Handle unhandled rejections and exceptions
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  // Log to error tracking service (Sentry, etc.)
  // Gracefully shutdown
  process.exit(1);
});

process.on('uncaughtException', error => {
  console.error('Uncaught Exception:', error);
  // Log to error tracking service
  process.exit(1); // MUST exit — state is corrupted
});

// ❌ HALLUCINATION TRAP: After uncaughtException, the process MUST exit
// The process is in an undefined state — continuing is dangerous
// ❌ process.on("uncaughtException", (err) => { console.log(err); }); // continues running
// ✅ process.on("uncaughtException", (err) => { log(err); process.exit(1); });
```

### Application Error Classes

```typescript
export class AppError extends Error {
  constructor(
    message: string,
    public statusCode: number = 500,
    public code: string = 'INTERNAL_ERROR',
    public isOperational: boolean = true,
  ) {
    super(message);
    this.name = 'AppError';
    Error.captureStackTrace(this, this.constructor);
  }
}

export class NotFoundError extends AppError {
  constructor(resource: string, id: string) {
    super(`${resource} '${id}' not found`, 404, 'NOT_FOUND');
  }
}

export class ValidationError extends AppError {
  constructor(
    message: string,
    public errors: Record<string, string[]> = {},
  ) {
    super(message, 400, 'VALIDATION_ERROR');
  }
}

export class UnauthorizedError extends AppError {
  constructor(message = 'Authentication required') {
    super(message, 401, 'UNAUTHORIZED');
  }
}

// Express error middleware
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  if (err instanceof AppError && err.isOperational) {
    res.status(err.statusCode).json({
      error: { code: err.code, message: err.message },
    });
  } else {
    // Programmer error — log and return generic message
    console.error('Unexpected error:', err);
    res.status(500).json({
      error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' },
    });
  }
});
```

---

## Async Patterns

### Parallel vs Sequential

```typescript
// ❌ SEQUENTIAL: Each await blocks the next
const users = await getUsers(); // 200ms
const posts = await getPosts(); // 200ms
const stats = await getStats(); // 200ms
// Total: 600ms

// ✅ PARALLEL: All start simultaneously
const [users, posts, stats] = await Promise.all([
  getUsers(), // 200ms
  getPosts(), // 200ms (concurrent)
  getStats(), // 200ms (concurrent)
]);
// Total: ~200ms

// Promise.allSettled — when some can fail
const results = await Promise.allSettled([
  fetchCriticalData(),
  fetchOptionalData(),
  fetchAnalytics(),
]);

for (const result of results) {
  if (result.status === 'fulfilled') {
    process(result.value);
  } else {
    console.error('Failed:', result.reason);
  }
}
```

### Retry Pattern

```typescript
async function withRetry<T>(
  fn: () => Promise<T>,
  options: { maxRetries?: number; baseDelay?: number; maxDelay?: number } = {},
): Promise<T> {
  const { maxRetries = 3, baseDelay = 1000, maxDelay = 10000 } = options;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries) throw error;

      const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
      const jitter = delay * (0.5 + Math.random() * 0.5);
      console.warn(`Attempt ${attempt + 1} failed, retrying in ${jitter}ms`);
      await new Promise(resolve => setTimeout(resolve, jitter));
    }
  }
  throw new Error('Unreachable');
}

// Usage:
const data = await withRetry(() => fetch('https://api.flaky.com/data'), {
  maxRetries: 3,
  baseDelay: 500,
});
```

### AbortController (Timeouts & Cancellation)

```typescript
async function fetchWithTimeout(url: string, timeoutMs = 5000): Promise<Response> {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch(url, { signal: controller.signal });
    return response;
  } catch (error) {
    if (error instanceof DOMException && error.name === 'AbortError') {
      throw new Error(`Request to ${url} timed out after ${timeoutMs}ms`);
    }
    throw error;
  } finally {
    clearTimeout(timeoutId);
  }
}
```

---

## Streaming

```typescript
import { Readable, Transform, pipeline } from 'node:stream/promises';
import { createReadStream, createWriteStream } from 'node:fs';
import { createGzip } from 'node:zlib';

// Stream large file processing (no memory issues)
async function processLargeCSV(inputPath: string, outputPath: string) {
  const transform = new Transform({
    transform(chunk, encoding, callback) {
      const processed = chunk.toString().toUpperCase();
      callback(null, processed);
    },
  });

  await pipeline(
    createReadStream(inputPath),
    transform,
    createGzip(),
    createWriteStream(outputPath),
  );
}

// Streaming HTTP response
app.get('/export', async (req, res) => {
  res.setHeader('Content-Type', 'text/csv');
  res.setHeader('Content-Disposition', 'attachment; filename=export.csv');

  const cursor = db.collection('users').find().stream();
  cursor.on('data', user => {
    res.write(`${user.id},${user.name},${user.email}\n`);
  });
  cursor.on('end', () => res.end());
  cursor.on('error', err => {
    console.error(err);
    res.status(500).end();
  });
});

// ❌ HALLUCINATION TRAP: Use stream/promises (not callbacks)
// ❌ const { pipeline } = require("stream");  ← callback-based
// ✅ import { pipeline } from "node:stream/promises";  ← async/await
```

---

## Environment & Config

```typescript
// config.ts — centralized, validated configuration
import { z } from 'zod';

const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
  REDIS_URL: z.string().url().optional(),
  JWT_SECRET: z.string().min(32),
  CORS_ORIGIN: z.string().default('http://localhost:5173'),
  LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
});

export const config = envSchema.parse(process.env);

// ❌ HALLUCINATION TRAP: Validate env vars at startup — fail FAST
// ❌ process.env.DATABASE_URL!  ← crashes at runtime, not startup
// ✅ Validate with Zod on app init — crash immediately with clear error

// ❌ HALLUCINATION TRAP: Never hardcode secrets
// ❌ const JWT_SECRET = "my-secret-key";  ← in source code
// ✅ const JWT_SECRET = process.env.JWT_SECRET;  ← from environment
```

---

## Security Hardening

```typescript
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';

// Security headers
app.use(helmet());

// Rate limiting
app.use(
  '/api/',
  rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100, // 100 requests per window
    standardHeaders: true,
    legacyHeaders: false,
    message: { error: 'Too many requests, try again later' },
  }),
);

// Auth rate limiting (stricter)
app.use(
  '/api/auth/',
  rateLimit({
    windowMs: 15 * 60 * 1000,
    max: 5, // only 5 login attempts per 15 min
  }),
);

// Input validation (ALWAYS validate)
app.post('/api/users', async (req, res, next) => {
  try {
    const data = CreateUserSchema.parse(req.body); // Zod validates
    const user = await createUser(data);
    res.status(201).json(user);
  } catch (error) {
    next(error);
  }
});

// SQL injection prevention (covered by parameterized queries)
// ❌ db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
// ✅ db.query("SELECT * FROM users WHERE id = $1", [req.params.id]);

// Path traversal prevention
import { resolve, normalize } from 'node:path';

function safePath(userInput: string, baseDir: string): string {
  const resolved = resolve(baseDir, normalize(userInput));
  if (!resolved.startsWith(baseDir)) {
    throw new Error('Path traversal detected');
  }
  return resolved;
}
```

---

## Process Management

### Graceful Shutdown

```typescript
async function gracefulShutdown(signal: string) {
  console.log(`\n${signal} received — shutting down gracefully`);

  // Stop accepting new connections
  server.close();

  // Close database connections
  await db.end();

  // Close Redis
  await redis.quit();

  // Allow in-flight requests 10s to complete
  setTimeout(() => {
    console.error('Forceful shutdown after timeout');
    process.exit(1);
  }, 10000);

  process.exit(0);
}

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
```

### Worker Threads (CPU-Bound)

```typescript
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';

if (isMainThread) {
  // Main thread — offload CPU work
  function runWorker(data: unknown): Promise<unknown> {
    return new Promise((resolve, reject) => {
      const worker = new Worker(new URL(import.meta.url), { workerData: data });
      worker.on('message', resolve);
      worker.on('error', reject);
    });
  }

  const result = await runWorker({ input: largeDataSet });
} else {
  // Worker thread — CPU-intensive work
  const result = heavyComputation(workerData.input);
  parentPort?.postMessage(result);
}

// ❌ HALLUCINATION TRAP: Worker threads are for CPU-bound tasks
// For I/O-bound tasks (network, file), use async/await — NOT workers
// Workers have overhead (serialization, memory) — don't overuse
```

## 🚨 Edge-Case & Failure Mode Matrix

| Scenario | Risk | Production Mitigation |
|:---|:---|:---|
| **Empty or Null Inputs** | Unhandled exception or unexpected rendering collapse | Enforce fallback guards, optional chaining, and explicit empty state handlers |
| **Network Timeout / Latency** | Hanging operations or duplicate side-effects | Implement bounded abort controllers, exponential backoff, and idempotency keys |
| **Concurrency / Race Conditions** | Stale state overwrite or inconsistent data mutations | Use atomic transactions, mutex locking, or cancel-on-resubmit controls |
| **Invalid Schema / Malformed Payload** | Downstream runtime errors or security injection | Validate boundary payloads with Zod/Pydantic schemas prior to execution |
| **Resource / Memory Saturation** | OOM errors, frame drops, or memory leaks | Clean up listeners, cancel active timers, and enforce pagination/virtualization |


## 🏛️ Tribunal Verification & Guardrails

**Active Reviewers:** `logic-reviewer` · `security-auditor` · `api-architect` · `resilience-reviewer`
**Slash Command:** `/review` or `/tribunal-full`

### 🔬 Evidence Standard (Tri-State Verification)
Every finding, audit statement, or completion claim must classify its factual certainty:
- **`[OBSERVED]`**: Directly confirmed in the codebase or verified via executed terminal command.
- **`[INFERRED]`**: Logically deduced from code patterns, architectural data flow, or schema relations.
- **`[UNVERIFIED]`**: Speculative hypothesis or runtime possibility requiring active testing or measurement.

### ✅ Pre-Flight Self-Audit Checklist
```
✅ Are all inputs and boundary payloads validated against schemas (Zod/Pydantic)?
✅ Are SQL and database queries parameterized with zero string concatenation?
✅ Are error boundaries and timeout/retry policies explicitly declared?
✅ Are authentication and object-level authorization (IDOR/BOLA) checked before business logic?
✅ Did I verify that imported dependencies exist in package manifests?
```

### 🛑 Verification-Before-Completion (VBC) Protocol
**CRITICAL:** You must follow a strict "evidence-based closeout" state machine.
- ❌ **Forbidden:** Declaring a task complete because the output "looks correct."
- ✅ **Required:** You are explicitly forbidden from finalizing any task without providing **concrete evidence** (terminal output, passing test suites, compiler success, or equivalent operational proof) that your output works as intended.


## Verification (Auto-Remediated)

- [ ] **Verify Execution**: Ensure the output matches the original task requirements.
- [ ] **Safety Check**: Validate that no destructive actions occurred outside the requested scope.
