NestJS Production Architecture
Pathrule2 Rules • 3 Memories • 1 Skill
NestJS gives large Node applications structure, but its dependency container can conceal expensive request-scoped graphs, circular module ownership, validation gaps, and business logic coupled to decorators or transport objects. This pattern constrains module boundaries and provider scope, records the roles of guards, pipes, interceptors, and filters, keeps application services transport-neutral, and adds a dependency-graph review procedure. It is distinct from Express and Hono API patterns because the central problem is Nest's container and metadata model, not raw middleware sequencing or a minimal router surface.
Suggested path map
Pathrule places each piece on the matching path, so your assistant only sees it where it belongs. This is the scoping you get on import; you can adjust it in your workspace.
Rules
2Make each module the owner of one capability/src/moduleshighstrictExport a narrow public provider surface and remove circular imports instead of normalizing them with container escape hatches.
| 1 | A Nest module is an ownership boundary. When every module imports every other module, providers become globally reachable, initialization order becomes fragile, and tests must boot half the application to exercise one capability. |
| 2 | |
| 3 | - Group controllers, providers, and persistence adapters by capability, then export only the application service or token other modules are allowed to call. |
| 4 | - Treat `forwardRef` as a design alarm. Extract the shared contract or move orchestration to a higher-level module instead of adding reciprocal imports. |
| 5 | - Avoid global modules for feature behavior. Reserve global providers for truly universal infrastructure such as configuration, tracing, or a clock abstraction. |
| 6 | - Test a feature module with its explicit imports and overrides so undeclared dependencies fail in isolation rather than appearing only in the full application boot. |
| 7 | |
| 8 | See /src/application for the adjacent decision or procedure that completes this constraint. |
Validate and transform every transport boundary/src/commonhighstrictUse explicit runtime schemas or DTO validation for HTTP, messages, configuration, and external payloads before application code runs.
| 1 | TypeScript types disappear at runtime, and Nest decorators do not make untrusted JSON valid. Invalid shapes must be rejected or transformed at the boundary before they reach services that rely on invariants. |
| 2 | |
| 3 | - Enable a global validation pipe with an explicit allowlist and rejection policy; do not let unexpected properties flow into persistence or command objects. |
| 4 | - Validate route parameters and query values as their intended scalar types instead of accepting strings and relying on implicit coercion deep in the service. |
| 5 | - Apply the same runtime validation to queue messages, scheduled inputs, environment configuration, and webhook payloads, not only controller bodies. |
| 6 | - Map validation failures to a stable public error shape while retaining structured internal details for diagnostics without exposing secrets or stack traces. |
| 7 | |
| 8 | See /src/application for the adjacent decision or procedure that completes this constraint. |
Memories
3Provider scope is a capacity decision/src/modulesSingleton is the default; request scope is reserved for state that cannot be passed explicitly and is measured across the dependency graph.
| 1 | Changing one provider to request scope can pull every consumer above it into per-request instantiation. The cost is not visible at the decorator, so scope must be treated as a graph-wide capacity choice rather than a convenience for accessing request data. |
| 2 | |
| 3 | - Keep stateless services singleton and pass actor, tenant, locale, or correlation context as method arguments when doing so keeps ownership clear. |
| 4 | - Use transient scope only when each injection site genuinely needs a separate stateful helper, and verify that lifecycle against retries and tests. |
| 5 | - If request scope is unavoidable, inspect which upstream providers inherit it and measure allocation and latency under representative concurrency. |
| 6 | - Do not store mutable per-request data on singleton providers. Use explicit context values or a bounded context mechanism with guaranteed cleanup. |
| 7 | |
| 8 | See /src/common for the rule or workflow that puts this decision into practice. |
Guards, pipes, interceptors, and filters have separate jobs/src/commonAuthorization, input conversion, cross-cutting execution, and exception translation stay in their intended Nest lifecycle stages.
| 1 | Nest offers several extension points that can all run around a controller, but using them interchangeably makes execution order and test ownership impossible to reason about. Each stage should answer one kind of question. |
| 2 | |
| 3 | - Guards decide whether the resolved actor may enter the handler. They should not perform the business transition or silently reshape the request. |
| 4 | - Pipes validate or transform an individual input before the handler. They do not fetch unrelated domain data or implement authorization policy. |
| 5 | - Interceptors wrap execution for cross-cutting behavior such as timing, serialization, caching, or tracing; they must preserve errors and cancellation. |
| 6 | - Exception filters translate known failures at the transport edge. They should not swallow unknown exceptions or turn every failure into a successful response envelope. |
| 7 | |
| 8 | See /src/modules for the rule or workflow that puts this decision into practice. |
Application services are independent of the active transport/src/applicationCommands and use cases accept plain typed inputs and return domain results, allowing HTTP, queues, and jobs to share behavior.
| 1 | Nest decorators are convenient at controllers and consumers, but application logic coupled to `Request`, `Response`, acknowledgement objects, or transport exceptions cannot be reused safely and is difficult to retry. |
| 2 | |
| 3 | - Translate transport input into a command before invoking the application service, including actor and idempotency context where required. |
| 4 | - Return a domain result or throw typed application failures. Let each adapter map those outcomes to HTTP status, message acknowledgement, or retry behavior. |
| 5 | - Inject repository and external-service ports through tokens so tests replace them without booting real network clients. |
| 6 | - Own the transaction around the complete use case and avoid holding it open while awaiting a slow remote dependency unless the consistency design explicitly requires it. |
| 7 | |
| 8 | See /src/modules for the rule or workflow that puts this decision into practice. |
Skills
1audit-nest-dependency-graph/rootReview Nest modules and provider scopes for cycles, hidden globals, request-scope expansion, and transport coupling.
| 1 | --- |
| 2 | name: audit-nest-dependency-graph |
| 3 | description: Audit a NestJS dependency graph after adding modules, providers, or a new transport adapter. |
| 4 | --- |
| 5 | |
| 6 | # Audit Nest Dependency Graph |
| 7 | |
| 8 | Run this procedure when the affected surface changes, before the result is promoted to production. Record evidence for every step instead of accepting a plausible-looking result. |
| 9 | |
| 10 | - [ ] Draw module imports and exports for the changed capability; identify reciprocal edges, global feature providers, and exports that expose implementation details. |
| 11 | - [ ] Trace every non-singleton provider upward through its consumers and estimate how many objects one request or message now creates. |
| 12 | - [ ] Inspect guards, pipes, interceptors, and filters for responsibilities that belong to application services or another lifecycle stage. |
| 13 | - [ ] Boot the feature module in isolation with test overrides and verify that every dependency is declared rather than inherited accidentally from the root app. |
| 14 | - [ ] Call the same application service from a non-HTTP adapter or a plain unit test and remove any request, response, or transport exception dependency that appears. |
| 15 | |
| 16 | ## Exit criteria |
| 17 | |
| 18 | The change is complete only when the expected behavior, failure behavior, and rollback path have all been exercised with representative data. Preserve the evidence with the change so the next operator can repeat the same checks. |
Why this pattern
AI agents often mark providers request-scoped without measuring the graph, use forwardRef to hide circular ownership, validate only TypeScript types, or place business logic in controllers and guards.
Built for TypeScript teams operating modular NestJS HTTP, message, or scheduled-work applications.
Keeps your assistant from:
- Expanding one request-scoped provider into an expensive dependency subtree
- Masking cyclic module ownership with repeated forwardRef calls
- Trusting compile-time DTO types for untrusted runtime input
- Coupling domain services to HTTP decorators and response objects
- License
- Apache-2.0
- Version
- 1.0.0
- Updated
- 2026-08-25