The real test of a backend isn't whether it works today — it's whether a new engineer can ship a feature next quarter without paging you. NestJS gives you the structure to make that possible, but only if you use its module system deliberately. Here are the conventions I keep reaching for.
One module per bounded responsibility
Each pipeline stage or domain concern lives as an isolated module with its own DTOs, services, and queue. In the AdShort AI backend, prompt generation, media synthesis, translation, and publishing were each their own module. A bad render can't poison the publish step, and you can scale the slow stage independently.
The boundary is the feature, not the technical layer. Resist the urge to have a giant 'services' folder — colocate everything a feature needs and the codebase stays navigable as it grows.
Make dependency direction explicit
Modules should depend inward toward shared domain primitives, never sideways into each other's internals. When two feature modules need to talk, they do it through a well-defined interface or an event — not by reaching into the other's services.
This is the rule that keeps a team change survivable. A new engineer can understand one module in isolation because its dependencies are declared, not discovered.
DTOs and validation at the edge
Every inbound request is validated against a DTO before it reaches business logic. The controller's job is to validate and delegate; the service's job is the actual work. Keeping validation at the edge means the core never has to defend against malformed input.
The small conventions compound
Consistent file naming, one export per file where it makes sense, and a predictable folder shape per module sound trivial. They're what let someone open a module they've never seen and know where everything is. Consistency is a feature.
Takeaways
- Organize modules around bounded responsibilities, not technical layers.
- Keep dependencies pointing inward; modules communicate through interfaces or events, never internals.
- Validate every request against a DTO at the controller edge so the core stays clean.
- Boring, consistent conventions are what make a codebase survive a team change.