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
In this repository19 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- custom-standardsSkill
- agent-code-to-confirmSkill
- agent-to-confirmSkill
- claude-guideline-creationSkill
- skill-creatorSkill
- youtube-to-guidelineSkill
- youtube-transcribeSkill
- fastapi-standardsSkill
- general-standardsSkill
- go-standardsSkill
- java-standardsSkill
- nextjs-standardsSkill
- python-standardsSkill
- react-standardsSkill
- reviewing-codebasesSkill
- rust-standardsSkill
- skill-toggleSkill
- typescript-standardsSkill
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
- Very specific: line 49
Every rule has a stable ID like NODE-ERR-3.
- Very specific: line 73
Every backend service therefore ALSO conforms to the separate typescript-standards.
- Very specific: line 94
NODE-BASE-3 (MUST): Use a single, committed lockfile (package-lock.json, pnpm-lock.yaml, or yarn.lock).
- Very specific: line 101
NODE-BASE-6 (SHOULD): Prefer Node built-ins over a dependency when they cover the need — global fetch, node:test, AbortController, …
- Distinctive: line 117
NODE-STRUCT-3 (MUST NOT): Create circular dependencies between modules.
- Very specific: line 119
NODE-STRUCT-4 (SHOULD): Keep cross-cutting concerns (logging, error types, config, auth middleware, generic utils) in a clearly named …
- Very specific: line 167
MUST include an ESLint config (flat eslint.config.js) and a Prettier config, and npm run lint MUST pass in CI.
- Very specific: line 175
NODE-STYLE-3 (MUST): Use const by default, let only when reassigned.
- Very specific: line 177
NODE-STYLE-4 (MUST): Use strict equality (=== / !==).
- Very specific: line 182
NODE-STYLE-6 (SHOULD): Keep functions small and single-purpose (soft guide: ≲ 40 lines, cyclomatic complexity ≲ 10, enforced via ESLint …
- Very specific: line 184
NODE-STYLE-7 (MUST NOT): Leave dead code, commented-out blocks, or console.log debugging in committed code (see §8 for logging).
- Very specific: line 191
NODE-ASYNC-1 (MUST): Use async/await for asynchronous flow.
- Very specific: line 193
NODE-ASYNC-2 (MUST NOT): Create floating promises — every promise MUST be awaited, returned, or explicitly handled.
- Very specific: line 196
NODE-ASYNC-3 (MUST): Run independent async work concurrently with Promise.all / Promise.allSettled, not sequentially in a loop, when the …
- Very specific: line 198
NODE-ASYNC-4 (SHOULD NOT): Use blocking synchronous calls (fs.readFileSync, crypto.*Sync, execSync, CPU-heavy loops) on the request path.
- Distinctive: line 229
NODE-ERR-5 (MUST): Never leak internal details (stack traces, SQL, secrets) to clients in production.
- Distinctive: line 270
NODE-SEC-3 (MUST NOT): Commit secrets, API keys, tokens, or credentials.
- Very specific: line 272
NODE-SEC-4 (MUST): Never use eval, new Function(userInput), or unsafe deserialization of untrusted data.
- Very specific: line 278
Use crypto.randomUUID() / crypto.randomBytes for tokens, never Math.random() for anything security-sensitive.
- Very specific: line 280
NODE-SEC-7 (MUST): Keep dependencies patched.
- Distinctive: line 284
NODE-SEC-9 (MUST): Never serialize sensitive fields (password/credential hashes, tokens, secrets, full PII) into responses or logs.
- Distinctive: line 292
NODE-CONF-1 (MUST): All environment-specific values come from configuration, never hardcoded.
- Distinctive: line 293
Read config in one place; the rest of the app imports the typed/validated config object.
- Very specific: line 305
NODE-LOG-1 (MUST NOT): Use console.log for application logging in production code.
- Distinctive: line 318
NODE-PERF-1 (MUST): Keep the event loop unblocked — no synchronous CPU-heavy work on the request path (mirrors NODE-ASYNC-4).
- Distinctive: line 321
NODE-PERF-2 (MUST): Treat services as stateless for horizontal scaling — no in-process session/state that would break across multiple …
- Distinctive: line 328
NODE-PERF-5 (MAY): Use clustering / the platform's process scaling to use multiple cores.
- Distinctive: line 339
NODE-TEST-3 (SHOULD): Follow the test pyramid — many fast unit tests, fewer integration tests, few end-to-end.
- Very specific: line 342
NODE-TEST-4 (SHOULD): Use a single test runner consistently — node:test, Jest, or Vitest.
- Distinctive: line 346
NODE-TEST-6 (MUST): Test error paths, not just the happy path — especially operational errors and input validation.
- Distinctive: line 357
NODE-DEP-1 (SHOULD): Add a dependency only when it earns its place.
- Very specific: line 370
NODE-DOC-1 (MUST): Every repo has a README covering purpose, prerequisites, setup, run, test, and env configuration — runnable from the …
- Distinctive: line 372
NODE-DOC-2 (SHOULD): Document the *why* in code comments, not the *what*.
- Very specific: line 374
NODE-DOC-3 (SHOULD): Keep an API contract (OpenAPI/Swagger) for HTTP services and keep it in sync with the code.
- Very specific: line 425
NODE-NEST-3 (MUST): Use dependency injection — declare collaborators as constructor parameters.
- Very specific: line 446
NODE-NEST-6 (MUST): Use Fastify plugins, not Express middleware.
- Very specific: line 454
NODE-NEST-8 (MUST): Read configuration through @nestjs/config with a validation schema (Joi or Zod) that fails startup on missing/invalid …
- Very specific: line 457
NODE-NEST-9 (SHOULD): Use a structured logger wired into Nest — nestjs-pino, or Fastify's built-in pino via new FastifyAdapter({ logger: …
- Very specific: line 460
NODE-NEST-10 (MUST): Put cross-cutting concerns in the correct Nest primitive, not in controllers: guards (authn/authz), interceptors …
- Very specific: line 466
NODE-NEST-12 (SHOULD): Test with the Nest testing module — unit-test providers via Test.createTestingModule({...}) with mocked dependencies.
- Distinctive: line 492
All resolved — recorded here as the decision log for this standard:
≈ “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.
Files
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 withfastapi.mdv1.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.mdplus split-outreferences/*.mdfiles (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/throttler6.5.x;@nestjs/config4.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 severity | Gate |
MUST/MUST NOT in Security | Critical | Blocks merge |
MUST/MUST NOT elsewhere | Critical / Warning (by impact) | Blocks merge if it risks correctness or data loss |
SHOULD without justification | Warning | Should fix |
MAY / stylistic preference | Suggestion | Nice 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.
- 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.
- Runtime: Node.js on a maintained Active LTS (even-numbered) line. Front-end build targets are out of scope.
- 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. - 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"). - 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. - Custom overrides: anything in the project's
custom-standardsskill overrides this document on conflict.
1. Runtime, Tooling & Project Baseline
- NODE-BASE-1 (MUST): Pin the Node version. Declare a supported range in
package.jsonengines.nodeAND 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, oryarn.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
packageManagerinpackage.json. Do not commit lockfiles from two different managers. - NODE-BASE-5 (MUST): Provide reproducible scripts in
package.json: at minimumstart,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/(orcommon/) 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, andnpm run lintMUST 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/jsrecommended, and the flat configs exported byeslint-plugin-n(the maintained successor to the deprecatedeslint-plugin-node),eslint-plugin-promise, andeslint-plugin-security. (Use each plugin's flat-config export, not the legacyextends: ["plugin:…/recommended"]eslintrc strings, which are incompatible with the flat config mandated by NODE-STYLE-1.) - NODE-STYLE-3 (MUST): Use
constby default,letonly when reassigned. MUST NOT usevar. - NODE-STYLE-4 (MUST): Use strict equality (
===/!==).==is allowed only for the deliberate== nullnull-or-undefined check, with a comment. - NODE-STYLE-5 (SHOULD): Naming —
camelCasefor variables/functions,PascalCasefor classes and error types,UPPER_SNAKE_CASEfor true constants, files inkebab-caseor the component conventionfeature.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.logdebugging in committed code (see §8 for logging).
4. Asynchronous Patterns
- NODE-ASYNC-1 (MUST): Use
async/awaitfor asynchronous flow. MUST NOT mixawaitwith.then()chains in the same function. - NODE-ASYNC-2 (MUST NOT): Create floating promises — every promise MUST be
awaited, returned, or explicitly handled. Enableno-floating-promises(via typescript-eslint where TS is used;promise/catch-or-returnotherwise). - 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/AbortSignalfor 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) carryingstatus,code, and anisOperationalflag. 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 {}, nocatchthat 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 onunhandledRejection. 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_processwith interpolated input (preferexecFilewith an args array overexec). - 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. Usecrypto.randomUUID()/crypto.randomBytesfor tokens, neverMath.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.examplelisting every variable (no real values). Real.envfiles 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.logfor 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
dependenciesfromdevDependenciescorrectly — build/test-only tools MUST NOT ship independencies. - 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
READMEcovering 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/swaggerfrom 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 rule Enforced 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-validatorDTOsNODE-ERR-3(centralized error handling)Global exception filter; throw HttpException/typed exceptionsNODE-SEC-5(secure headers / CORS / rate limit / body cap)@fastify/helmet,enableCors,@nestjs/throttlerNODE-PERF-3(graceful shutdown)app.enableShutdownHooks()+ lifecycle hooksNODE-CONF-1/2(single validated config)@nestjs/configwith 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 howNODE-STRUCT-1/2are 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
newa service inside another. Depend on interfaces/tokens where it aids testing. (Enforces low coupling andNODE-TEST-2isolation.) NODE-NEST-4 (MUST): Validate all inbound payloads with DTO classes +
class-validatordecorators, enforced by a globalValidationPipeconfigured withwhitelist: trueandforbidNonWhitelisted: true(reject unknown fields) andtransform: true. This is the concrete form ofNODE-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 ofNODE-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 — notapp.use(helmet())),@fastify/compress. Configure CORS viaapp.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-expressrecipes 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 inNODE-SEC-5.) - NODE-NEST-8 (MUST): Read configuration through
@nestjs/configwith a validation schema (Joi or Zod) that fails startup on missing/invalid values; injectConfigServicerather than readingprocess.envthroughout. (Concrete form ofNODE-CONF-1/2.) - NODE-NEST-9 (SHOULD): Use a structured logger wired into Nest —
nestjs-pino, or Fastify's built-in pino vianew FastifyAdapter({ logger: true }). Noconsole.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 implementOnModuleDestroy/OnApplicationShutdownto drain and close DB pools, queues, and clients. (Concrete form ofNODE-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 inNODE-TEST-3. - NODE-NEST-13 (MUST): Because services are TypeScript, they ALSO conform to
typescript-standards(strict mode, noanyescape 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/jwtwith apassport-jwtstrategy (ExtractJwt.fromAuthHeaderAsBearerToken()), behind aJwtAuthGuard. 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 withFASTAPI-SEC-1.) - NODE-NEST-15 (SHOULD): Version the public API with URI versioning —
app.enableVersioning({ type: VersioningType.URI })and@Version('1')(or a/v1prefix). Header/media-type versioning is an acceptable alternative; internal-only APIs MAY defer until a v2 is actually needed. (Consistent withFASTAPI-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 withFASTAPI-STRUCT-5.)
Open Questions for your input
All resolved — recorded here as the decision log for this standard:
| # | Question | Decision |
| Q1 | Language / module system | ✅ Backend services are TypeScript (NestJS-first); module system follows the Nest/TS build. Standalone JS scripts use ESM. (Assumptions #3–#4.) |
| Q2 | Web framework | ✅ NestJS on the Fastify adapter mandated for HTTP services (§13 overlay). (Assumption #5.) |
| Q3 | Strictness dial | ✅ The MUST / SHOULD / MAY split as-is is the right altitude. |
| Q4 | Coverage threshold | ✅ Keep ≥ 80% on changed code (NODE-TEST-5) AND flag weak/assertion-free tests (NODE-TEST-7). |
| Q5 | Rule-ID + severity scheme | ✅ Locked — ID format FRAMEWORK-AREA-N and the severity-mapping table are propagated to all nine standards as-is. |
| Q6 | Depth | ✅ Current level of detail is right — keep each standard lean like this. |
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
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.

