This template favors clarity and consistency over cleverness. Keep changes small, predictable, and easy for new devs to follow.
- Prefer small, single-purpose modules.
- Keep functions short and linear; extract helpers when logic grows.
- Use explicit names over short names.
- Avoid heavy comments; add only when intent isn’t obvious from types.
- Use
stricttyping; avoidany. - Prefer
typefor object shapes andinterfacefor public contracts. - Optional fields imply
undefinedby default.
undefined= omitted / not provided.null= explicitly empty value (e.g., not found, no session).- Normalize DB
NULLtoundefinedin the domain model unless a field is explicitly nullable in the domain.
- Controllers: HTTP I/O only (parse, validate, call service, respond).
- Services: business logic, orchestration, and domain rules.
- Repositories: persistence only.
- Publishers: emit domain events, no business logic.
- Infra: external systems (DB, queue, auth adapters).
- Use feature routers per domain area (e.g.,
notes.routes.ts,auth.routes.ts). - Mount them in a single composition point (e.g.,
src/http/routes.ts). - Keep route handlers thin and delegate to controllers.
- Throw
AppErrorsubclasses for expected failures. - Central error handler maps them to JSON responses.
- Log unexpected errors with request id.
- Log structured JSON in prod.
- Use pretty logs only in dev (optional dependency).
- Include
requestIdin error logs where possible.
- Unit: pure functions/services with fakes.
- Integration: real DB + repositories.
- E2E: app + HTTP + real DB.
- Prefer short inline comments near non-obvious behavior.
- Avoid JSDoc unless public API needs it.