# Node.js Conventions (server-side)

Load this **in addition to** [javascript-typescript.md](../languages/javascript-typescript.md). JS / TS rules still apply; this file adds the server-specific layer.

## Architecture

- Separate layers: **HTTP / transport** → **business / domain** → **data access**. No layer skips downward; no layer reaches upward.
- Request handlers are thin: parse input → call a service → format response.
- Business logic is framework-agnostic. Do **not** import Express / Fastify / Nest types in service or domain modules.
- One file, one concern. A 1000-line `index.ts` is a structural bug.
- Dependency injection (constructor or factory) over module-level singletons. Singletons are not testable.

## Error handling

- Distinguish **operational errors** (network, validation, expected business state) from **programmer errors** (bugs, broken invariants). Recover from operational, crash on programmer.
- Use `async` / `await` with `try` / `catch` at the layer that knows what to do. Catching just to rethrow with no extra value is a smell.
- Centralise error handling in middleware. Every route returns through it.
- Always handle event-emitter `'error'` events — an unhandled emitter error crashes the process.
- Throw subclasses of `Error` (`class ValidationError extends Error {}`) so handlers can `instanceof` them.
- Process-level: `process.on('unhandledRejection', ...)` and `process.on('uncaughtException', ...)` should log and exit gracefully. Do not swallow.

## Async discipline

- Never mix callbacks with promises in the same flow. Use `util.promisify` at the boundary, once.
- Prefer `for await...of` over manual cursor / stream loops.
- `Promise.all` for independent parallel work, `Promise.allSettled` when one failure must not cancel the rest.
- Set explicit timeouts on **every** outgoing HTTP / DB call (no infinite waits, no defaults).
- Use `AbortController` to cancel in-flight work when the upstream caller goes away.

## Security baseline

- All configuration in environment variables (loaded via `dotenv` in dev only). Validate them at startup with `zod`, `envalid`, or similar — fail fast on missing / invalid values.
- Never log secrets, auth-endpoint request bodies, full JWTs, or PII.
- HTTPS only at the edge. Behind a TLS-terminating proxy is fine; never speak plain HTTP across the public internet.
- Use `helmet` for security headers, `cors` with an explicit allowlist (no `*` in production).
- Validate every untrusted input (`zod`, `joi`, `valibot`). Validation belongs at the boundary, not inside services.
- Rate-limit public endpoints (`express-rate-limit` or upstream proxy).
- Run `npm audit` / `pnpm audit` regularly. Patch promptly.

## Performance

- **Streams** (`fs.createReadStream`, `pipeline`) for large payloads — never load multi-MB files fully into memory.
- Multi-core: `cluster`, PM2, or a process manager. Pure async will not save you from a single-thread CPU bottleneck.
- Keep request handlers off the event loop. Offload heavy CPU work to a `worker_thread` or a queue.
- Connection pool for every DB client. `pg`, `mysql2`, and `mongoose` default to a pool — do **not** create a client per request.
- Cache idempotent reads at an explicit layer (Redis, in-memory LRU). Do not bolt caching into a service method.
- Use `Buffer`/typed arrays for binary data, not strings.

## Logging

- Structured logger (`pino`, `winston`). Emit **JSON**, not human strings.
- Every request: request id, method, path, status, latency. Propagate the request id through downstream calls via `AsyncLocalStorage`.
- Levels: `error` (pageable), `warn` (suspicious), `info` (business event), `debug` (off in production).
- Never `console.log` in production code. `console.log` exists for ad-hoc REPL only.

## Tests

- Unit-test services and domain functions with zero Node-runtime dependencies — no `fs`, no `net`, no real DB.
- Integration-test the HTTP layer with `supertest` against the real Express / Fastify app instance.
- `testcontainers` for DBs when the production DB has features (locks, full-text, JSONB, partitioning) you depend on. In-memory fakes lie about behaviour.
- Test the error paths. Coverage of the happy path only is not coverage.

## Formatter / linter / tools

- Same as JS / TS: `prettier`, `eslint`, `tsc`.
- `npm run lint && npm test` (or the project's equivalent) must pass before any commit.
