agentleFS
motola10motola10/claude-code-base/.claude/skills/nodejs-standards/SKILL.md

nodejs-standards

Skill1 starsChanged 5 months agoMITView on GitHub

Best-practice coding standards for Node.js backend services (manifest: package.json with server deps; default stack NestJS on the Fastify adapter, TypeScript). Referenced by the review agents. Covers structure, error handling, async, security, logging, auth, testing, plus a NestJS overlay. TypeScript Node services ALSO load typescript-standards. Do NOT use for React/frontend code.

  • Reads credentials
  • Installs packages

The file

Show its rules and text

We split this skill into the rules it gives an agent

41 rules. About 0 are common, 14 are distinctive, and 27 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈14

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 27

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Very specific:

    Every rule has a stable ID like NODE-ERR-3.

    line 49
  2. Very specific:

    Every backend service therefore ALSO conforms to the separate typescript-standards.

    line 73
  3. Very specific:

    NODE-BASE-3 (MUST): Use a single, committed lockfile (package-lock.json, pnpm-lock.yaml, or yarn.lock).

    line 94
  4. Very specific:

    NODE-BASE-6 (SHOULD): Prefer Node built-ins over a dependency when they cover the need — global fetch, node:test, AbortController, …

    line 101
  5. Distinctive:

    NODE-STRUCT-3 (MUST NOT): Create circular dependencies between modules.

    line 117
  6. Very specific:

    NODE-STRUCT-4 (SHOULD): Keep cross-cutting concerns (logging, error types, config, auth middleware, generic utils) in a clearly named …

    line 119
  7. Very specific:

    MUST include an ESLint config (flat eslint.config.js) and a Prettier config, and npm run lint MUST pass in CI.

    line 167
  8. Very specific:

    NODE-STYLE-3 (MUST): Use const by default, let only when reassigned.

    line 175
  9. Very specific:

    NODE-STYLE-4 (MUST): Use strict equality (=== / !==).

    line 177
  10. Very specific:

    NODE-STYLE-6 (SHOULD): Keep functions small and single-purpose (soft guide: ≲ 40 lines, cyclomatic complexity ≲ 10, enforced via ESLint …

    line 182
  11. Very specific:

    NODE-STYLE-7 (MUST NOT): Leave dead code, commented-out blocks, or console.log debugging in committed code (see §8 for logging).

    line 184
  12. Very specific:

    NODE-ASYNC-1 (MUST): Use async/await for asynchronous flow.

    line 191
  13. Very specific:

    NODE-ASYNC-2 (MUST NOT): Create floating promises — every promise MUST be awaited, returned, or explicitly handled.

    line 193
  14. Very specific:

    NODE-ASYNC-3 (MUST): Run independent async work concurrently with Promise.all / Promise.allSettled, not sequentially in a loop, when the …

    line 196
  15. Very specific:

    NODE-ASYNC-4 (SHOULD NOT): Use blocking synchronous calls (fs.readFileSync, crypto.*Sync, execSync, CPU-heavy loops) on the request path.

    line 198
  16. Distinctive:

    NODE-ERR-5 (MUST): Never leak internal details (stack traces, SQL, secrets) to clients in production.

    line 229
  17. Distinctive:

    NODE-SEC-3 (MUST NOT): Commit secrets, API keys, tokens, or credentials.

    line 270
  18. Very specific:

    NODE-SEC-4 (MUST): Never use eval, new Function(userInput), or unsafe deserialization of untrusted data.

    line 272
  19. Very specific:

    Use crypto.randomUUID() / crypto.randomBytes for tokens, never Math.random() for anything security-sensitive.

    line 278
  20. Very specific:

    NODE-SEC-7 (MUST): Keep dependencies patched.

    line 280
  21. Distinctive:

    NODE-SEC-9 (MUST): Never serialize sensitive fields (password/credential hashes, tokens, secrets, full PII) into responses or logs.

    line 284
  22. Distinctive:

    NODE-CONF-1 (MUST): All environment-specific values come from configuration, never hardcoded.

    line 292
  23. Distinctive:

    Read config in one place; the rest of the app imports the typed/validated config object.

    line 293
  24. Very specific:

    NODE-LOG-1 (MUST NOT): Use console.log for application logging in production code.

    line 305
  25. Distinctive:

    NODE-PERF-1 (MUST): Keep the event loop unblocked — no synchronous CPU-heavy work on the request path (mirrors NODE-ASYNC-4).

    line 318
  26. Distinctive:

    NODE-PERF-2 (MUST): Treat services as stateless for horizontal scaling — no in-process session/state that would break across multiple …

    line 321
  27. Distinctive:

    NODE-PERF-5 (MAY): Use clustering / the platform's process scaling to use multiple cores.

    line 328
  28. Distinctive:

    NODE-TEST-3 (SHOULD): Follow the test pyramid — many fast unit tests, fewer integration tests, few end-to-end.

    line 339
  29. Very specific:

    NODE-TEST-4 (SHOULD): Use a single test runner consistently — node:test, Jest, or Vitest.

    line 342
  30. Distinctive:

    NODE-TEST-6 (MUST): Test error paths, not just the happy path — especially operational errors and input validation.

    line 346
  31. Distinctive:

    NODE-DEP-1 (SHOULD): Add a dependency only when it earns its place.

    line 357
  32. Very specific:

    NODE-DOC-1 (MUST): Every repo has a README covering purpose, prerequisites, setup, run, test, and env configuration — runnable from the …

    line 370
  33. Distinctive:

    NODE-DOC-2 (SHOULD): Document the *why* in code comments, not the *what*.

    line 372
  34. Very specific:

    NODE-DOC-3 (SHOULD): Keep an API contract (OpenAPI/Swagger) for HTTP services and keep it in sync with the code.

    line 374
  35. Very specific:

    NODE-NEST-3 (MUST): Use dependency injection — declare collaborators as constructor parameters.

    line 425
  36. Very specific:

    NODE-NEST-6 (MUST): Use Fastify plugins, not Express middleware.

    line 446
  37. Very specific:

    NODE-NEST-8 (MUST): Read configuration through @nestjs/config with a validation schema (Joi or Zod) that fails startup on missing/invalid …

    line 454
  38. Very specific:

    NODE-NEST-9 (SHOULD): Use a structured logger wired into Nest — nestjs-pino, or Fastify's built-in pino via new FastifyAdapter({ logger: …

    line 457
  39. Very specific:

    NODE-NEST-10 (MUST): Put cross-cutting concerns in the correct Nest primitive, not in controllers: guards (authn/authz), interceptors …

    line 460
  40. Very specific:

    NODE-NEST-12 (SHOULD): Test with the Nest testing module — unit-test providers via Test.createTestingModule({...}) with mocked dependencies.

    line 466
  41. Distinctive:

    All resolved — recorded here as the decision log for this standard:

    line 492

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Node.js Coding Standard (Reference Template v1.1 — LOCKED)

Status: ✅ Locked — all six review questions resolved (see end of doc). This is now the reference template: its shape (constraint language, rule IDs, severity mapping, good/bad examples, core + overlay structure) is reused across the other standards. Still amendable later via the normal change process — add new rule IDs, never renumber.

v1.1 (2026-06-13): added cross-platform API best practices to the NestJS overlay — NODE-NEST-14 (JWT-bearer auth default), NODE-NEST-15 (URI API versioning), NODE-NEST-16 (mature ORM + migrations; TypeORM/Prisma recommended). Kept consistent with fastapi.md v1.0.

Structure: an agnostic core (NODE-* rules, §1–§12) that applies to any backend Node code — scripts, workers, Lambdas — plus a mandated framework overlay (NODE-NEST-* rules, §13) for HTTP services. Decision locked: backend HTTP services use NestJS on the Fastify adapter, in TypeScript. The overlay mechanically enforces the core.

On adoption this becomes .claude/skills/nodejs-standards/SKILL.md plus split-out references/*.md files (the overlay → references/nestjs-fastify-rules.md). While in review it is kept as one self-contained document.

Toolchain verified current as of 2026-06-12 (via Context7 + npm + endoflife.date): the active (non-EOL) Node.js LTS lines are 22 ("Jod") and 24 ("Krypton", current) — Node 20 ("Iron") reached EOL 2026-04-30, and 26 becomes LTS in Oct 2026; NestJS 11.1.x (@nestjs/core, @nestjs/platform-fastify); Fastify 5.8.x; @nestjs/throttler 6.5.x; @nestjs/config 4.0.x. Rules below are written version-agnostically; these are reference points, not pins.


How to read this document

Constraint language (RFC 2119)

  • MUST / MUST NOT — hard requirement. A violation is a blocking issue.
  • SHOULD / SHOULD NOT — strong recommendation. Deviation is allowed only with a written, in-code justification (e.g. a // Justification: comment or PR note).
  • MAY — optional; a legitimate choice with no penalty either way.

Rule IDs

Every rule has a stable ID like NODE-ERR-3. The Code Reviewer agent MUST cite the rule ID in its findings so each comment is auditable and traceable back to this document. IDs are stable — never renumber an existing rule; add new ones with the next free number.

Severity mapping (how a violation surfaces in a review)

This document says…Reviewer severityGate
MUST/MUST NOT in SecurityCriticalBlocks merge
MUST/MUST NOT elsewhereCritical / Warning (by impact)Blocks merge if it risks correctness or data loss
SHOULD without justificationWarningShould fix
MAY / stylistic preferenceSuggestionNice to have

Scope & Assumptions (stated explicitly)

These are the assumptions this standard makes. If any is wrong for your context, flag it — it changes the rules below.

  1. Target: server-side / backend Node.js services and CLIs (APIs, workers, tooling). Front-end React/Next.js code is governed by their own standards, not this one.
  2. Runtime: Node.js on a maintained Active LTS (even-numbered) line. Front-end build targets are out of scope.
  3. Language: TypeScript is the baseline for backend services (NestJS is TypeScript-first). Every backend service therefore ALSO conforms to the separate typescript-standards; where the two overlap, the stricter rule wins. Plain-JS utility scripts are permitted but follow the agnostic core only.
  4. Module system: follows the NestJS/TypeScript build defaults (tsc/SWC compilation); do not hand-tune the module system per file. Standalone (non-Nest) JS scripts use ESM ("type": "module").
  5. Web framework (DECIDED): backend HTTP services use NestJS on the Fastify adapter (@nestjs/platform-fastify). The agnostic core (§1–§12) still governs all Node code; the mandated overlay (§13) governs every HTTP service. Non-HTTP code (workers, CLIs, scripts) applies the core only.
  6. Custom overrides: anything in the project's custom-standards skill overrides this document on conflict.

1. Runtime, Tooling & Project Baseline

  • NODE-BASE-1 (MUST): Pin the Node version. Declare a supported range in package.json engines.node AND commit a .nvmrc (or .tool-versions). CI MUST run on the pinned version.
  • NODE-BASE-2 (MUST NOT): Depend on End-of-Life or odd-numbered "Current" Node lines for production. Track the current Active LTS; plan upgrades before LTS end-of-life.
  • NODE-BASE-3 (MUST): Use a single, committed lockfile (package-lock.json, pnpm-lock.yaml, or yarn.lock). CI MUST install with the frozen-lockfile flag (npm ci / pnpm i --frozen-lockfile).
  • NODE-BASE-4 (SHOULD): One package manager per repo, declared via packageManager in package.json. Do not commit lockfiles from two different managers.
  • NODE-BASE-5 (MUST): Provide reproducible scripts in package.json: at minimum start, dev, test, lint, build (if applicable). A new contributor should run the project from these alone.
  • NODE-BASE-6 (SHOULD): Prefer Node built-ins over a dependency when they cover the need — global fetch, node:test, AbortController, structuredClone, node --watch, crypto.randomUUID(). Add a library only when it earns its weight (see §11).

2. Project Structure & Architecture

  • NODE-STRUCT-1 (SHOULD): Organize by feature/component, not by technical type. Group a feature's files together rather than scattering them across global controllers/, services/, models/ folders.
  • NODE-STRUCT-2 (MUST): Enforce 3-tier layering within a component and respect the dependency direction: web/transport → service (business logic) → repository (data access).
    • The web layer (controllers/routes) MUST NOT contain business logic or talk to the DB directly.
    • The service layer MUST NOT know about HTTP (req/res), and MUST NOT contain raw DB queries.
    • The repository layer is the only place that talks to the database/external store.
  • NODE-STRUCT-3 (MUST NOT): Create circular dependencies between modules. The reviewer flags any import cycle.
  • NODE-STRUCT-4 (SHOULD): Keep cross-cutting concerns (logging, error types, config, auth middleware, generic utils) in a clearly named shared/ (or common/) area, not duplicated.
src/
├── components/
│   ├── users/
│   │   ├── users.controller.js   # web layer: parse req, call service, shape response
│   │   ├── users.service.js      # business logic: no req/res, no raw queries
│   │   ├── users.repository.js   # data access only
│   │   └── users.test.js
│   └── orders/ …
└── shared/
    ├── middleware/
    ├── errors/                   # AppError + error types
    └── config/
// ✅ Layering respected
// users.controller.js  (web)
async function getUser(req, res, next) {
  try { res.json(await usersService.getById(req.params.id)); }
  catch (err) { next(err); }            // delegate to centralized handler
}
// users.service.js  (business logic — no req/res)
async function getById(userId) {
  const user = await usersRepository.findById(userId);
  if (!user) throw new AppError('User not found', { status: 404, code: 'USER_NOT_FOUND' });
  return sanitize(user);                // strip secrets before returning
}
// users.repository.js  (data access only)
function findById(id) { return db.users.findOne({ _id: id }); }
// ❌ Anti-pattern: controller does DB + business logic, leaks password
async function getUser(req, res) {
  const user = await db.users.findOne({ _id: req.params.id }); // NODE-STRUCT-2 violation
  res.json(user);                                              // NODE-SEC-9 violation: leaks password field
}

3. Code Style & Conventions

  • NODE-STYLE-1 (MUST): Style is enforced by tooling, not by reviewers' opinions. The repo MUST include an ESLint config (flat eslint.config.js) and a Prettier config, and npm run lint MUST pass in CI. Formatting comments in review are out of scope — the formatter decides.
  • NODE-STYLE-2 (SHOULD): Compose the flat config from a recognized baseline plus the Node/security/promise plugins — @eslint/js recommended, and the flat configs exported by eslint-plugin-n (the maintained successor to the deprecated eslint-plugin-node), eslint-plugin-promise, and eslint-plugin-security. (Use each plugin's flat-config export, not the legacy extends: ["plugin:…/recommended"] eslintrc strings, which are incompatible with the flat config mandated by NODE-STYLE-1.)
  • NODE-STYLE-3 (MUST): Use const by default, let only when reassigned. MUST NOT use var.
  • NODE-STYLE-4 (MUST): Use strict equality (=== / !==). == is allowed only for the deliberate == null null-or-undefined check, with a comment.
  • NODE-STYLE-5 (SHOULD): Naming — camelCase for variables/functions, PascalCase for classes and error types, UPPER_SNAKE_CASE for true constants, files in kebab-case or the component convention feature.layer.js. Be consistent within the repo.
  • NODE-STYLE-6 (SHOULD): Keep functions small and single-purpose (soft guide: ≲ 40 lines, cyclomatic complexity ≲ 10, enforced via ESLint complexity). Long functions are a refactor signal.
  • NODE-STYLE-7 (MUST NOT): Leave dead code, commented-out blocks, or console.log debugging in committed code (see §8 for logging).

4. Asynchronous Patterns

  • NODE-ASYNC-1 (MUST): Use async/await for asynchronous flow. MUST NOT mix await with .then() chains in the same function.
  • NODE-ASYNC-2 (MUST NOT): Create floating promises — every promise MUST be awaited, returned, or explicitly handled. Enable no-floating-promises (via typescript-eslint where TS is used; promise/catch-or-return otherwise).
  • NODE-ASYNC-3 (MUST): Run independent async work concurrently with Promise.all / Promise.allSettled, not sequentially in a loop, when the operations don't depend on each other.
  • NODE-ASYNC-4 (SHOULD NOT): Use blocking synchronous calls (fs.readFileSync, crypto.*Sync, execSync, CPU-heavy loops) on the request path. Offload CPU-bound work to worker threads or a queue.
  • NODE-ASYNC-5 (SHOULD): Support cancellation and timeouts with AbortController/AbortSignal for outbound I/O (fetch, DB drivers that accept a signal) on request-scoped work.
// ❌ Sequential — three round-trips back to back (NODE-ASYNC-3)
const user = await getUser(id);
const orders = await getOrders(id);
const prefs = await getPrefs(id);

// ✅ Concurrent — independent work in parallel
const [user, orders, prefs] = await Promise.all([getUser(id), getOrders(id), getPrefs(id)]);

5. Error Handling

  • NODE-ERR-1 (MUST): Distinguish operational errors (expected: bad input, 404, upstream timeout) from programmer errors (bugs: undefined access, bad type). Operational errors are handled and surfaced cleanly; programmer errors should crash the process and be fixed.
  • NODE-ERR-2 (MUST): Throw a single typed error class (e.g. AppError extends Error) carrying status, code, and an isOperational flag. MUST NOT throw strings or plain objects.
  • NODE-ERR-3 (MUST): Route all request errors to one centralized error-handling middleware. Controllers catch and delegate via next(err); they do not format error responses themselves.
  • NODE-ERR-4 (MUST NOT): Swallow errors — no empty catch {}, no catch that only logs and continues when the caller needed the result. If you catch, you must handle, rethrow, or meaningfully degrade (with a comment explaining why).
  • NODE-ERR-5 (MUST): Never leak internal details (stack traces, SQL, secrets) to clients in production. Return a safe message + error code; log the full detail server-side.
  • NODE-ERR-6 (MUST): Handle process-level safety nets: log and exit on uncaughtException; log on unhandledRejection. Pair this with graceful shutdown (§9) and a process manager/orchestrator that restarts the process.
// shared/errors/app-error.js
export class AppError extends Error {
  constructor(message, { status = 500, code = 'INTERNAL_ERROR', isOperational = true } = {}) {
    super(message);
    this.name = 'AppError';
    this.status = status; this.code = code; this.isOperational = isOperational;
    Error.captureStackTrace?.(this, AppError);
  }
}

// shared/middleware/error-handler.js  (registered LAST)
export function errorHandler(err, req, res, _next) {
  logger.error({ msg: err.message, code: err.code, status: err.status, stack: err.stack,
                 url: req.url, method: req.method, userId: req.user?.id });
  const isProd = process.env.NODE_ENV === 'production';
  if (err.isOperational) {
    return res.status(err.status ?? 500)
              .json({ status: 'error', code: err.code, message: err.message,
                      ...(!isProd && { stack: err.stack }) });
  }
  return res.status(500).json({ status: 'error', code: 'INTERNAL_ERROR',
                               message: isProd ? 'Internal server error' : err.message });
}

6. Security (violations here are Critical / blocking)

  • NODE-SEC-1 (MUST): Validate and sanitize all external input (body, params, query, headers) at the boundary with a schema validator (Zod, Joi, or ajv). Reject unknown/extra fields.
  • NODE-SEC-2 (MUST NOT): Build SQL/NoSQL queries or shell commands by string concatenation with user input. Use parameterized queries / an ORM, and avoid child_process with interpolated input (prefer execFile with an args array over exec).
  • NODE-SEC-3 (MUST NOT): Commit secrets, API keys, tokens, or credentials. Secrets come from environment/secret manager only (§7). The reviewer treats any hardcoded secret as a blocking finding.
  • NODE-SEC-4 (MUST): Never use eval, new Function(userInput), or unsafe deserialization of untrusted data.
  • NODE-SEC-5 (MUST): For HTTP services — set secure headers (e.g. helmet), enforce CORS deliberately (no blanket * on credentialed endpoints), apply rate limiting on public endpoints, and cap request body size.
  • NODE-SEC-6 (MUST): Hash passwords with a slow algorithm (bcrypt/argon2) — never MD5/SHA-1. Use crypto.randomUUID() / crypto.randomBytes for tokens, never Math.random() for anything security-sensitive.
  • NODE-SEC-7 (MUST): Keep dependencies patched. CI MUST run npm audit (or equivalent) and fail on known high/critical advisories that have a fix available.
  • NODE-SEC-8 (SHOULD): Apply least privilege — the process, its DB user, and its file/network access should have only the permissions they need.
  • NODE-SEC-9 (MUST): Never serialize sensitive fields (password/credential hashes, tokens, secrets, full PII) into responses or logs. Strip them at the boundary — e.g. a sanitize() in the service layer or an explicit allowlist DTO — rather than returning raw persistence objects.

7. Configuration & Secrets

  • NODE-CONF-1 (MUST): All environment-specific values come from configuration, never hardcoded. Read config in one place; the rest of the app imports the typed/validated config object.
  • NODE-CONF-2 (MUST): Validate config at startup against a schema and fail fast (exit non-zero) on missing/invalid required values — don't discover a missing var at request time.
  • NODE-CONF-3 (MUST): Provide a committed .env.example listing every variable (no real values). Real .env files MUST be git-ignored.
  • NODE-CONF-4 (SHOULD): Load env via dotenv (or Node's --env-file) in dev only; in production, inject via the platform/secret manager.

8. Logging & Observability

  • NODE-LOG-1 (MUST NOT): Use console.log for application logging in production code. Use a structured logger (pino, winston).
  • NODE-LOG-2 (MUST): Log structured JSON with a level, a timestamp, and correlation context (request id / trace id). MUST NOT log secrets, passwords, tokens, full card/PII data.
  • NODE-LOG-3 (SHOULD): Emit a correlation/request ID per request and propagate it to downstream calls and logs.
  • NODE-LOG-4 (SHOULD): Expose a health/readiness endpoint and basic metrics for production services.

9. Performance, Scalability & Lifecycle

  • NODE-PERF-1 (MUST): Keep the event loop unblocked — no synchronous CPU-heavy work on the request path (mirrors NODE-ASYNC-4). Use streams for large payloads instead of buffering whole files/responses in memory.
  • NODE-PERF-2 (MUST): Treat services as stateless for horizontal scaling — no in-process session/state that would break across multiple instances. Use a shared store (Redis/DB) for shared state.
  • NODE-PERF-3 (MUST): Implement graceful shutdown — on SIGTERM/SIGINT, stop accepting new work, finish in-flight requests within a timeout, close DB/connection pools, then exit.
  • NODE-PERF-4 (SHOULD): Reuse connection pools and clients (DB, HTTP); do not open a new connection per request.
  • NODE-PERF-5 (MAY): Use clustering / the platform's process scaling to use multiple cores; prefer the orchestrator (PM2, container replicas, k8s) over hand-rolled clustering.

10. Testing

  • NODE-TEST-1 (MUST): New or changed behavior ships with tests. The reviewer/QA agent treats changed logic with no corresponding test as insufficient coverage.
  • NODE-TEST-2 (MUST): Tests are deterministic and isolated — no reliance on real external network/services in unit tests (mock them), no order dependency, no shared mutable state between tests.
  • NODE-TEST-3 (SHOULD): Follow the test pyramid — many fast unit tests, fewer integration tests, few end-to-end. Cover the service layer thoroughly; cover the controller layer via integration tests.
  • NODE-TEST-4 (SHOULD): Use a single test runner consistently — node:test, Jest, or Vitest. Name tests by behavior: describe('UsersService.getById') / it('throws 404 when user missing').
  • NODE-TEST-5 (SHOULD): Target ≥ 80% coverage on changed code (not whole-repo). Coverage is a floor, not the goal — don't chase the number on trivial code, and don't test framework internals.
  • NODE-TEST-6 (MUST): Test error paths, not just the happy path — especially operational errors and input validation.
  • NODE-TEST-7 (MUST): Tests MUST make meaningful assertions. A test that executes code but asserts nothing (or only asserts it "doesn't throw") inflates the coverage number without verifying behavior — the reviewer/QA agent flags such weak/assertion-free tests as a finding, even when the 80% bar (NODE-TEST-5) is met. Coverage is necessary, not sufficient.

11. Dependencies & Supply Chain

  • NODE-DEP-1 (SHOULD): Add a dependency only when it earns its place. Prefer a small, focused, maintained package over a large transitive tree; prefer a Node built-in over a package (NODE-BASE-6).
  • NODE-DEP-2 (MUST): Separate dependencies from devDependencies correctly — build/test-only tools MUST NOT ship in dependencies.
  • NODE-DEP-3 (MUST): Pin via lockfile (NODE-BASE-3) and keep dependencies current via a managed process (e.g. Renovate/Dependabot), not ad-hoc.
  • NODE-DEP-4 (SHOULD NOT): Introduce packages that are unmaintained, deprecated, or have known unpatched high/critical advisories.

12. Documentation

  • NODE-DOC-1 (MUST): Every repo has a README covering purpose, prerequisites, setup, run, test, and env configuration — runnable from the README alone.
  • NODE-DOC-2 (SHOULD): Document the why in code comments, not the what. Public/exported functions with non-obvious contracts get a short JSDoc (params, returns, thrown errors).
  • NODE-DOC-3 (SHOULD): Keep an API contract (OpenAPI/Swagger) for HTTP services and keep it in sync with the code. For NestJS services, generate it with @nestjs/swagger from the DTOs/decorators.

13. Framework Overlay — NestJS on the Fastify Adapter (MANDATED for HTTP services)

This overlay applies to every backend HTTP service. It does not replace the core — it makes the core's rules structurally enforced by the framework. Standalone workers/CLIs/scripts skip this section and follow §1–§12 only.

How the overlay enforces the core — the reviewer should treat a core violation as "more severe" when the framework already provides the mechanism:

Core ruleEnforced by (NestJS/Fastify mechanism)
NODE-STRUCT-2 (web→service→repository layering)Feature modules: controllers (web) → injectable providers (logic) → repositories (data)
NODE-SEC-1 (validate all inbound)Global ValidationPipe + class-validator DTOs
NODE-ERR-3 (centralized error handling)Global exception filter; throw HttpException/typed exceptions
NODE-SEC-5 (secure headers / CORS / rate limit / body cap)@fastify/helmet, enableCors, @nestjs/throttler
NODE-PERF-3 (graceful shutdown)app.enableShutdownHooks() + lifecycle hooks
NODE-CONF-1/2 (single validated config)@nestjs/config with a validation schema
  • NODE-NEST-1 (MUST): Bootstrap on the Fastify adapter and type the app as NestFastifyApplication. Do not use the Express adapter (@nestjs/platform-express) for new services.

    import { NestFactory } from '@nestjs/core';
    import { ValidationPipe } from '@nestjs/common';
    import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
    import helmet from '@fastify/helmet';
    import { AppModule } from './app.module';
    
    async function bootstrap() {
      const app = await NestFactory.create<NestFastifyApplication>(
        AppModule,
        new FastifyAdapter({ logger: true }), // Fastify's built-in pino logger
      );
      app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }));
      app.enableShutdownHooks();
      await app.register(helmet);              // Fastify plugin, not Express middleware
      await app.listen(process.env.PORT ?? 3000, '0.0.0.0');
    }
    bootstrap();
  • NODE-NEST-2 (MUST): Organize by feature module — one @Module() per feature bundling its controller, provider(s)/service(s), and repository. This is how NODE-STRUCT-1/2 are enforced. Controllers handle transport only (parse/return); services hold business logic and know nothing of HTTP; repositories are the only DB touchpoint.
  • NODE-NEST-3 (MUST): Use dependency injection — declare collaborators as constructor parameters; never new a service inside another. Depend on interfaces/tokens where it aids testing. (Enforces low coupling and NODE-TEST-2 isolation.)
  • NODE-NEST-4 (MUST): Validate all inbound payloads with DTO classes + class-validator decorators, enforced by a global ValidationPipe configured with whitelist: true and forbidNonWhitelisted: true (reject unknown fields) and transform: true. This is the concrete form of NODE-SEC-1.

    // create-message.dto.ts
    import { IsEmail, IsNotEmpty, IsString, MaxLength } from 'class-validator';
    export class CreateMessageDto {
      @IsString() @IsNotEmpty() @MaxLength(4096) body!: string;
      @IsEmail() recipient!: string;
    }
  • NODE-NEST-5 (MUST): Centralize errors with a global exception filter; controllers/services throw HttpException (or typed domain exceptions mapped to one). Controllers MUST NOT hand-format error responses. This is the concrete form of NODE-ERR-3/NODE-ERR-5 (the filter strips internals in production).
  • NODE-NEST-6 (MUST): Use Fastify plugins, not Express middleware. Register infra/security plugins via app.register(...): @fastify/helmet (as a plugin — not app.use(helmet())), @fastify/compress. Configure CORS via app.enableCors({ ... }) or the create options — under NestJS 11 with Fastify, methods beyond the CORS-safelisted defaults MUST be listed explicitly. Express-only middleware and @nestjs/platform-express recipes do not apply.
  • NODE-NEST-7 (MUST): Rate-limit with @nestjs/throttler (ThrottlerModule) configured globally, with stricter limits on auth and public/webhook endpoints. (Concrete form of the rate-limit clause in NODE-SEC-5.)
  • NODE-NEST-8 (MUST): Read configuration through @nestjs/config with a validation schema (Joi or Zod) that fails startup on missing/invalid values; inject ConfigService rather than reading process.env throughout. (Concrete form of NODE-CONF-1/2.)
  • NODE-NEST-9 (SHOULD): Use a structured logger wired into Nest — nestjs-pino, or Fastify's built-in pino via new FastifyAdapter({ logger: true }). No console.log (NODE-LOG-1). Attach a request/correlation id.
  • NODE-NEST-10 (MUST): Put cross-cutting concerns in the correct Nest primitive, not in controllers: guards (authn/authz), interceptors (logging/transform/caching/timeout), pipes (validation/transform), exception filters (errors).
  • NODE-NEST-11 (MUST): Enable graceful shutdown — call app.enableShutdownHooks() and implement OnModuleDestroy/OnApplicationShutdown to drain and close DB pools, queues, and clients. (Concrete form of NODE-PERF-3.)
  • NODE-NEST-12 (SHOULD): Test with the Nest testing module — unit-test providers via Test.createTestingModule({...}) with mocked dependencies; e2e-test controllers against the Fastify app instance. Match the test pyramid in NODE-TEST-3.
  • NODE-NEST-13 (MUST): Because services are TypeScript, they ALSO conform to typescript-standards (strict mode, no any escape hatches, typed DTOs/return types). On overlap, the stricter rule wins.
  • NODE-NEST-14 (MUST): Protect non-public endpoints with auth enforced via guards (not ad-hoc checks). Default scheme: stateless JWT bearer tokens — @nestjs/passport + @nestjs/jwt with a passport-jwt strategy (ExtractJwt.fromAuthHeaderAsBearerToken()), behind a JwtAuthGuard. Check authorization (roles/scopes via a roles guard), not just authentication. Sanctioned alternatives by context: httpOnly session cookies for browser-first apps, or an external IdP / OAuth2 provider for larger systems. (Cross-platform consistent with FASTAPI-SEC-1.)
  • NODE-NEST-15 (SHOULD): Version the public API with URI versioning — app.enableVersioning({ type: VersioningType.URI }) and @Version('1') (or a /v1 prefix). Header/media-type versioning is an acceptable alternative; internal-only APIs MAY defer until a v2 is actually needed. (Consistent with FASTAPI-DOC-2.)
  • NODE-NEST-16 (SHOULD): Access relational data through a mature ORM with versioned migrations, kept behind the repository layer (NODE-NEST-2) — never raw query strings in controllers (NODE-SEC-2). Recommended: TypeORM (@nestjs/typeorm, native repository pattern) or Prisma — both are first-class NestJS integrations. ORM library is recommended not mandated, but migrations + the data-layer boundary are required. (Consistent with FASTAPI-STRUCT-5.)

Open Questions for your input

All resolved — recorded here as the decision log for this standard:

#QuestionDecision
Q1Language / module system✅ Backend services are TypeScript (NestJS-first); module system follows the Nest/TS build. Standalone JS scripts use ESM. (Assumptions #3–#4.)
Q2Web framework✅ NestJS on the Fastify adapter mandated for HTTP services (§13 overlay). (Assumption #5.)
Q3Strictness dial✅ The MUST / SHOULD / MAY split as-is is the right altitude.
Q4Coverage threshold✅ Keep ≥ 80% on changed code (NODE-TEST-5) AND flag weak/assertion-free tests (NODE-TEST-7).
Q5Rule-ID + severity scheme✅ Locked — ID format FRAMEWORK-AREA-N and the severity-mapping table are propagated to all nine standards as-is.
Q6Depth✅ Current level of detail is right — keep each standard lean like this.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.