Skip to content

Lifecycle Hooks Execution Order does not respect Provider Dependencies within the Same Module #14773

@ori88c

Description

@ori88c

Is there an existing issue that is already proposing this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe it

NestJS ensures a topological instantiation order for providers based on their dependency graph (i.e., new MyProvider(...) respects dependencies). However, this ordering is not enforced for asynchronous lifecycle hooks, such as:

Describe the solution you'd like

Feature Request

Lifecycle hooks within a module should execute sequentially in a topological (for init) and reverse-topological (for teardown) order to ensure:

  • During initialization: A provider’s dependencies have fully initialized (onModuleInit resolved) before its own onModuleInit executes.
  • During teardown: A provider’s dependents have fully torn down (onModuleDestroy resolved) before its own onModuleDestroy executes.

This would improve lifecycle orchestration and prevent potential race-conditions.

Teachability, documentation, adoption, migration strategy

Ideally, the Lifecycle Hooks documentation will explicitly clarify the following points:

  • Module Initialization Order: Modules are initialized sequentially in topological order during the init phase and reverse-topological order during teardown.
  • Provider Initialization: Within each module, providers' onModuleInit hooks are invoked sequentially in topological order.
  • Provider Teardown: Within each module, providers' onModuleDestroy hooks are invoked sequentially in reverse-topological order.

This clarification will help developers better understand the precise execution flow of lifecycle hooks.

What is the motivation / use case for changing the behavior?

Comparison with Other Popular Dependency-Injection Framework

My suggestion aligns with Spring's default behavior:
https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html
"The order of startup and shutdown invocations can be important. If a "depends-on" relationship exists between any two objects, the dependent side starts after its dependency, and it stops before its dependency".

Use Case Example

Consider a module with two providers:

  1. A Kafka client provider that establishes a connection in onModuleInit().
  2. A Kafka publisher provider (built on top of the client) that creates a test topic in onModuleInit().

Issue

Since both providers belong to the same module, Nest does not guarantee a topological execution order for their lifecycle hooks. This leads to potential failures:

  • Initialization (onModuleInit): The publisher may attempt to publish before the client has established a connection.
  • Teardown (onModuleDestroy): The publisher may attempt to send messages after the client has disconnected.

Current Approach to Mitigate the Issue

Developers can implement custom lifecycle coordination layers, but this undermines NestJS’s built-in orchestration capabilities. A practical workaround is to place each provider with an asynchronous onModuleInit or onModuleDestroy hook into its own dedicated module - effectively creating one-provider modules.
However, this approach introduces significant downsides: excessive modularity and fragmentation, as logically related providers become scattered across multiple modules.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions