---
name: "Node.js"
description: "Always-on rules for writing Node.js back-end services in TypeScript or JavaScript."
applyTo: "**/*.ts,**/*.mts,**/*.js,**/*.mjs"
---

# Node.js rules

> Scope note: this pattern matches every TypeScript and JavaScript file. In repos that mix front-end and back-end code, narrow `applyTo` to the server folders (for example `"apps/api/**/*.ts,server/**/*.ts"`) so these rules do not apply to browser code.

## Project setup

- Use TypeScript with `strict: true` and `noUncheckedIndexedAccess`; no `any`, use `unknown` and narrow.
- Use ESM (`"type": "module"`, `NodeNext`) for new projects; do not mix ESM and CommonJS in one package.
- Keep `app.ts` (builds the app) separate from `server.ts` (listens, handles signals). Enables in-process tests.
- Target the active Node LTS or the version pinned in `engines`/`.nvmrc`.

## Input, errors and responses

- Parse every body, query, param and header with a schema (Zod or TypeBox; `ValidationPipe` with `whitelist` in NestJS). Never trust `req.body`.
- Infer types from the schema; do not hand-write a parallel interface.
- Throw typed domain errors (`NotFoundError`, `ConflictError`) and map them to HTTP status in one central error handler.
- Never send stack traces, SQL or internal messages to clients; return a stable problem+json body.
- Always `await` or `return` promises; no floating promises. Unhandled rejections crash the process.
- Validate or explicitly shape responses so fields like `passwordHash` never leak.

## Config and secrets

- Load config once in `config.ts`, validated by a schema at startup; do not read `process.env` throughout the code.
- Never commit secrets or `.env` files. Use Azure Key Vault or pipeline secret variables in deployed environments.

## Data and outbound calls

- Use parameterized queries or the ORM query builder; never build SQL from string templates with input.
- Scope every query by owner or tenant (`WHERE id = $1 AND tenant_id = $2`) to prevent IDOR.
- Wrap multi-step writes in a transaction; no HTTP calls or queue publishes inside it.
- Set a timeout on every outbound HTTP call (`AbortSignal.timeout`) and retry only idempotent requests with backoff.
- Never fetch user-supplied URLs server-side without a host allow-list. Prevents SSRF.

## Runtime safety

- Never block the event loop: no `*Sync` fs/crypto calls on request paths, no large synchronous JSON or regex work on untrusted input.
- Handle `SIGTERM`: stop accepting requests, drain in-flight work, close DB pools, then exit.
- Add authentication and a declared authorization check to every route; deny by default.
- Use `helmet`, rate limiting on auth and expensive routes, and a request body size limit.
- Use structured logging with pino and a request id; never log tokens, passwords or PII, and never use `console.log` in services.
- Run background work in a queue worker (e.g. BullMQ), not in `setTimeout` from a request.

## Dependencies and tests

- Commit the lockfile and install with `npm ci` in pipelines; review `npm audit` findings before adding a package.
- Prefer the platform (`fetch`, `node:test`, `crypto.randomUUID`) over small single-purpose packages.
- Test routes in-process (`app.inject` or Supertest) with a real database in a container for data-access code.

Go deeper: for larger tasks use the nodejs-development, nodejs-architecture, nodejs-testing, nodejs-performance and api-design skills, and backend-code-review before opening a pull request.
