Skip to content

NestJS Feature Selection

Put a concern where its required context and lifecycle semantics exist.

Responsibility matrix

FeatureAppropriate responsibilityCommon misuse
ModuleEncapsulation, provider wiring, public exportsOne global module exporting everything
ProviderApplication/domain/infrastructure behaviorRequest state stored in a singleton field
MiddlewareRaw request preprocessing, correlation IDsRoute authorization or business validation
GuardAuthentication/authorization decisionResponse mapping or business transaction
PipeValidate/transform handler argumentsDatabase writes or external calls
InterceptorWrap execution, trace, time, map compatible responsesHidden feature policy in a global interceptor
Exception filterMap uncaught failures to a transport responseSwallowing errors or retrying business operations
Param decoratorExtract established request contextPerforming I/O or authorization secretly
Controller/resolverAdapt transport to an application operationOwning invariants or querying several repositories
EventNotify independent reactions to a completed factRequest/response operation requiring an immediate result
QueueDurable/deferred/bursty workHiding a required synchronous result
SchedulerTime-triggered orchestrationRunning once per replica unintentionally
Lifecycle hookStart/stop managed resourcesStarting untracked work without shutdown behavior

Binding scope

Choose the narrowest level that represents the policy:

  • Global: an invariant for every matching request, such as baseline validation or tracing.
  • Controller: a policy shared by all routes in one transport adapter.
  • Route: behavior unique to one operation.
  • Parameter: transformation/validation of one value.

Global components increase blast radius. Test ordering and error behavior before changing one.

When dependency injection is needed globally, register the component through Nest's provider mechanism (for example APP_GUARD, APP_PIPE, APP_INTERCEPTOR, or APP_FILTER) instead of constructing it manually outside the container.

Authentication and authorization

Authentication establishes a principal. Authorization decides whether that principal can perform the operation on the target resource.

Typical split:

  1. strategy/middleware/guard validates credentials and establishes a typed principal;
  2. metadata declares coarse route policy when useful;
  3. guard or application policy enforces authorization;
  4. application/repository queries remain tenant/owner scoped as defense in depth.

Do not rely on a valid token as proof the user owns a requested resource.

Validation

Validate untrusted transport data with DTO/schema tooling at the boundary. Use explicit transformation and reject unknown fields when the public contract requires it.

Validation proves shape and basic constraints. Domain construction still enforces business invariants and current-state rules.

Cross-cutting behavior

Use interceptors for concerns that truly wrap many handlers: trace spans, request duration, response envelope mapping, compatible cache integration. Keep authorization in guards and argument conversion in pipes so order remains understandable.

Errors thrown by pipes, handlers, or providers skip to exception handling. Avoid duplicated error mapping in every controller.

Events, queues, and schedules

  • In-process event: fast, non-durable, same process only.
  • Durable queue/broker: persisted work across processes; duplicates and reordering may occur.
  • Scheduler: a trigger, not a distributed lock. Multiple replicas can execute the same schedule.

For scheduled work in a replicated deployment, choose one:

  • platform scheduler invokes an idempotent endpoint/job;
  • dedicated scheduler deployment with one replica;
  • distributed lock with lease/expiry and observable ownership;
  • enqueue a uniquely keyed job and let queue deduplication serialize it.

Lifecycle hooks

Use initialization hooks for async setup that must finish before readiness. Enable shutdown hooks when the process must receive termination signals through Nest. Close servers, consumers, pools, and telemetry in a known order.

Request-scoped providers do not receive lifecycle hooks; do not depend on them for resource cleanup.

Request lifecycle review

For HTTP, verify the effective order of:

  1. global and module middleware;
  2. global, controller, and route guards;
  3. global, controller, and route interceptors on entry;
  4. global, controller, route, and parameter pipes;
  5. handler and providers;
  6. route, controller, and global interceptors on return;
  7. route, controller, and global filters for uncaught exceptions.

See the official request lifecycle and lifecycle events documentation.


Canonical source: skills/nestjs-features-performance/references/feature-selection.md. This page is generated during the documentation build.

Open-source guidance for deliberate NestJS engineering.