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#
text
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
interface for object shapes (2-5x faster type resolution than intersections)
- Prefer
unknown over any; forces explicit narrowing
- Use
z.infer<typeof Schema> as single source of truth; never duplicate types and schemas
- Minimize
as assertions; 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; } in types/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 --noEmit passes with zero errors
npm test passes 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: