Skip to main content

Layout

Modules

Every new feature goes in src/modules/{module}/:
index.ts is a curated contract, not a barrel export. Only what other modules legitimately need is exported; everything else is internal. Cross-module code imports from the contract, never from another module’s services/ directly.
“No barrel exports” means no index.ts that mechanically re-exports a whole directory. A hand-picked public API is the opposite of that and is the required pattern.

The shared layer

src/lib/ holds what genuinely crosses modules:

Route conventions

Boundary rules

A route handler that calls prisma directly has put business logic in the transport layer. Route handlers orchestrate; services decide.
Modules with AI, XLSX or e-mail services keep them behind the contract and never import them into client components. The observability logger in lib/ is intentionally client-safe and does not import the App Insights sink; the module-level logger does, and is server-only.
Read process.env at a module boundary and pass values in as parameters. A service that reads its own configuration cannot be tested without the environment.
Parse external input — HTTP bodies, database JSON, third-party API responses — at the entry point, then pass typed values inward.

Size limits

Component files are limited to 200 lines by convention, warned past 200 by ESLint, and failed past 800 unless the file is grandfathered in a baseline. Grandfathered files may shrink but never grow — the ratchet only turns one way.

Events

Modules communicate through the event bus rather than by importing each other:
Names are {module}.{entity}.{action}. Handler registration happens through side-effect imports in the instrumentation entry point, not through the module contract — see the pre-boarding orchestrator for the reference implementation.