Skip to content

NestJS-Native Patterns

NestJS already implements many architectural and behavioral patterns. Use its lifecycle and container instead of creating competing mechanisms.

Classify by semantics, not by name

Nest metadata decorators such as @UseGuards() are not implementations of the GoF object Decorator pattern. A class extending Passport's PassportStrategy is a framework integration, but it demonstrates an application Strategy only when consumers select among real interchangeable authentication policies behind a stable contract. Likewise, a default-scoped provider has a shared container lifetime; it does not need a hand-written static Singleton implementation.

Name the actual mechanism and guarantee. A familiar pattern label must not imply durability, distribution, replay, independent scaling, or substitution that the implementation does not provide.

Module as encapsulation and composition root

@Module() declares controllers, providers, imports, and exports. The exports are the module's public facade. Provider registrations choose concrete implementations behind tokens.

Do not create a second manual dependency container inside a module.

Provider and dependency injection

Providers express constructor-injected collaboration. Custom providers support class, value, alias, and factory construction.

Use provider tokens for application ports and configuration. Avoid runtime service location unless implementing a plugin/discovery framework.

Middleware

Use for raw request/response concerns that occur before Nest's route context is available, such as correlation identifiers or compatible low-level middleware.

Do not use middleware for route authorization; guards have execution context and metadata.

Guard

Use for whether a principal/request may proceed. Guards can read route/controller metadata and work across supported execution contexts.

Keep authorization policy reusable. A guard adapts request context and calls the policy; it should not contain database-heavy business workflows by default.

Pipe

Use for validation and transformation of handler arguments. Global DTO validation protects transport input; parameter pipes parse focused values.

Do not put side effects or business transactions in pipes.

Interceptor

Use around handler execution for timing, tracing, response mapping, cache integration, or consistent cross-cutting behavior. Interceptors wrap the return path in reverse order.

Do not hide feature-specific business decisions in a global response interceptor.

Exception filter

Use at the transport boundary to map uncaught exceptions into protocol-specific responses and logging. Domain/application errors should remain transport-neutral when the architecture requires multiple adapters.

Avoid catch-and-rethrow filters that add no mapping, context, or observability.

Custom decorator

Use parameter decorators to extract already-established request context and metadata decorators to declare policies. A decorator should not perform hidden I/O or business mutations.

Dynamic module

Use for configurable reusable infrastructure. Prefer normal feature modules for business capabilities.

CQRS handlers

Commands, queries, events, and sagas are appropriate when explicit dispatch, separate models, or long-running workflows provide value. Do not use a command bus as a mandatory wrapper around every service call.

Dispatch alone does not queue, persist, replay, or audit a command. Add those guarantees explicitly when required. CQRS can separate read and write models, but it is not automatically more scalable than a cohesive CRUD/application-service design.

Event emitter versus broker

  • In-process emitter: best-effort reactions in the same process.
  • Queue/broker: durable or independently scaled work across processes.
  • Transactional outbox: database commit and external publication must stay consistent.

Do not assume an in-process event survives a crash or reaches another replica.

The same caution applies to an in-process CQRS EventBus: the event class and handler decorators do not by themselves create distributed pub/sub or reliable delivery.

Adapter-neutral application code

Use ExecutionContext in framework-level guards/interceptors that support HTTP, GraphQL, WebSockets, or microservices. Keep application operations independent of all transport context when practical.

Lifecycle order

The general HTTP path is middleware → guards → interceptors (before) → pipes → controller/provider → interceptors (after) → filters on uncaught exceptions.

Put a concern where the required information and semantics exist. Misplacing it creates duplication or surprising order dependencies.

See the official request lifecycle and provider documentation.


Canonical source: skills/nestjs-oop-design-patterns/references/nestjs-native-patterns.md. This page is generated during the documentation build.

Open-source guidance for deliberate NestJS engineering.