Node.js Backend
Verify before implementing: For framework-specific APIs (Express 5, Fastify 5, Node.js 22+ built-ins), look up current, version-matched official docs before writing code. Prefer Context7 (query-docs) when available. Otherwise use available web tools to read the framework's official documentation, or the Node.js API docs for Node.js built-ins. Training data may lag current releases.
Working rules
- Validate request and third-party data before use; keep response serialization and error envelopes explicit.
- Preserve caller-visible contracts and authorization when adding resilience or fallbacks.
- Bound concurrency, set timeouts, and avoid blocking production request paths.
- Verify actual resource identity before parsing or caching a reused client's result.
- Exercise operational telemetry and failure paths, not successful return codes alone.
Architecture
src/
├── routes/ # HTTP: parse request, call service, format response
├── middleware/ # Auth, validation, rate limiting, logging
├── services/ # Business logic (no HTTP types)
├── repositories/ # Data access only (queries, ORM)
├── config/ # Env, DB pool, constants
└── types/ # Shared TypeScript interfaces
- Routes never contain business logic
- Services never import Request/Response
- Repositories never throw HTTP errors
- Dependencies point inward only (Clean Architecture rule): routes -> services -> repositories. Never the reverse.
- For scripts/prototypes: single file is fine; ask "will this grow?"
TypeScript Rules
- Use
import type { }for type-only imports; eliminates runtime overhead - Prefer
interfacefor object shapes (2-5x faster type resolution than intersections) - Prefer
unknownoverany; forces explicit narrowing - Use
z.infer<typeof Schema>as single source of truth; never duplicate types and schemas - Minimize
asassertions; use type guards instead - Add explicit return types to exported functions (faster declaration emit)
- Untyped package?
declare module 'pkg' { const v: unknown; export default v; }intypes/ambient.d.ts
Discipline
- Simplicity first: every change as simple as possible, impact minimal code
- Only touch what's necessary; avoid introducing unrelated changes
- No hacky workarounds: if a fix feels wrong, step back and implement the clean solution
- Before adding a new abstraction, verify it appears in 3+ places. If not, inline it.
- If a fix requires bypassing TypeScript (
as any, non-null assertions on untrusted data,// @ts-ignore), treat it as a design smell and find the typed solution
Verify
tsc --noEmitpasses with zero errorsnpm testpasses with zero failures- No TypeScript bypasses (
as any,@ts-ignore) in new code
References
- TypeScript config: tsconfig, ESM, branded types, compiler performance
- Security: JWT, password hashing, rate limiting, OWASP
- API design patterns: pagination, filtering, sorting, deprecation, idempotency-key claim and retention
- Database & production: connection pooling, transactions, Docker, logging
Task-specific references
Read the relevant reference before implementing or reviewing the matching behavior:
- For framework choice, input validation, API contracts, or errors: api-boundaries.md.
- For concurrency, networking, startup, caches, lifecycle cleanup, or telemetry: async-and-production.md.
- For span kinds, HTTP-status-to-span-status rules, sampling placement, metric cardinality, or telemetry data governance: observability-tracing.md.
Existing specialized references, when the corresponding topic applies:
