Course outline

NestJS / Advanced: lifetime and distributed-work evidence / Lesson 13

Provider scopes, context and cycle design

Align instance lifetime with mutable state and avoid using scope changes as a substitute for clear ownership.

About 4 min readContent reviewed
On this page

Lesson goals

  • Explain singleton, request and transient lifetimes in a dependency graph.
  • Separate file cycles, module cycles and provider cycles.

Scope determines who shares an instance

A default-scoped provider is ordinarily shared within its owning application context. Request scope creates instances tied to a request context, while transient scope provides instances according to consumer resolution rather than one global shared instance. These are lifetime choices, not automatic authorization boundaries. A singleton may safely hold immutable configuration or an application-owned pool, but a mutable currentPrincipal property can mix overlapping requests. Choose state ownership before changing scope. A service receiving a request-scoped dependency can affect the scope of its consumers, so review the whole graph rather than one decorator in isolation.

Use request scope only for actual request state

The fragment declares a request-scoped context holder. It assumes an upstream integration supplies validated principal data and does not implement authentication. Do not place a new database pool inside every such instance. Keep shared resources in appropriately owned providers and pass narrow operation context to them. HTTP, RPC and long-lived transports establish contexts differently; use the documented context and injection token for the actual platform. A Nest 12 feature supported in one transport should not be generalized into an assumption that every context behaves identically. This example was not instantiated by a container.

typescript Illustrative request-lifetime holder
@Injectable({scope:Scope.REQUEST})
export class OperationContext {
  principal!: Principal;
}

Decorator imports and principal establishment are omitted. Request scope controls lifetime; it does not verify the principal.

Keep two synthetic operation states independent

The local factory returns a new state object for each call. Incrementing one context does not change the other, making the intended isolation observable without Nest. This is a normal object-allocation property, not proof that the framework creates a request-scoped provider correctly. A real test should resolve contexts under the selected container APIs and check the actual identity and dependency graph. Avoid treating a fresh wrapper as isolation if it still points to a shared mutable principal object. Ownership includes nested references as well as the outer instance.

javascript Complete offline context-isolation model
function operation(principalId) {return {principalId,calls:0};}
const a=operation("p1"), b=operation("p2");
a.calls++;
console.assert(a !== b && a.calls === 1 && b.calls === 0);
console.assert(b.principalId === "p2");

The two allocated objects remain independent. No Nest context ID or scoped dependency was resolved.

Fix the right dependency graph

A circular TypeScript file import can yield an unavailable runtime token, a module cycle can obstruct composition, and provider constructors can depend on one another. Diagnose which graph failed before using forwardRef everywhere. Extract shared contracts into dependency-light files and move cross-domain orchestration to an owner that depends on both sides. Deferred references can help supported container scenarios but do not establish a useful domain boundary or deterministic constructor order. Constructors should not perform order-sensitive external work. Keep initialization and resource acquisition in explicit lifecycle or factory contracts with controlled failure behavior.

Verify scope cost and lifecycle claims

Request scope can create more objects and change where dependencies are instantiated, but no universal performance penalty or tuning prescription follows from that alone. Measure approved representative workloads after correctness and ownership are clear. Verify lifecycle hooks for the chosen scope and Nest version; do not assume every per-request object receives application shutdown callbacks. Long-lived jobs should carry explicit minimal context rather than retaining an HTTP request object. This course runs the local allocation model only and does not resolve scopes, execute hooks or benchmark a container. Test those boundaries separately with authorized synthetic integrations.

Common errors and fixes

One request sees another principal

Why it happens
A singleton retained mutable request state.
How to fix it
Use proper operation context and avoid shared mutable identity.
Prevent it next time
Test overlapping principals and nested references.

Every request opens a new source pool

Why it happens
Shared resource ownership followed request scope accidentally.
How to fix it
Keep pools application-owned and inject narrow operations.
Prevent it next time
Review the entire scope dependency chain.

forwardRef does not fix an unavailable token

Why it happens
The fault was a file initialization or export issue, not the assumed provider cycle.
How to fix it
Identify and repair the actual graph.
Prevent it next time
Keep token declarations dependency-light.

Check your understanding

1. Why can two fresh context wrappers still leak state?

Show worked solution

They may share a nested mutable principal or cache reference. Fresh outer identity does not deep-copy dependencies. Define which values are immutable and which belong to one operation, then test nested changes explicitly. The local model avoids that problem with text, but a real provider graph must verify its own references.

2. Is making every provider request-scoped a safe default?

Show worked solution

No. It can duplicate shared resources, alter dependency lifetimes and obscure ownership without implementing authorization. Choose scope from actual mutable state and resource needs, preserve domain policy and verify container behavior. There is no universal scope setting that replaces those design decisions.

Key takeaways

  • Scope is a lifetime contract, not an access-control shortcut.
  • Diagnose cycles by graph and preserve explicit resource ownership.

Official references