NestJS / Intermediate: policies, adapters and sources / Lesson 7
Custom providers, configuration and async startup
Construct capabilities from validated configuration and make asynchronous readiness part of dependency ownership.
On this page
Lesson goals
- Distinguish useValue, useClass, useExisting and useFactory.
- Fail early on invalid configuration without disclosing secrets.
Choose a provider form for the intended identity
A value provider supplies an existing value, a class provider asks the container to construct an implementation, an alias provider can reuse another registration, and a factory computes a capability from injected dependencies. These forms have different instance and lifecycle implications. useExisting is useful when two tokens should name the same managed instance; useClass is not simply another spelling for aliasing. Document who owns cleanup and whether the value may contain mutable state. A configuration object should not become an accidental place to store the current principal or per-request counters.
Keep factory dependencies explicit
The fragment creates a narrow options value from an injected configuration service. It does not open a source connection. inject lists the factoryโs dependencies in argument order, and the owning module must make them visible. An async factory can return a promise that Nest awaits before dependent construction, but waiting forever is not a readiness strategy. Define bounded startup and safe errors in the actual adapter. Avoid reading environment variables all over the domain; central validation makes missing or malformed values reproducible and keeps secrets out of controller code.
const catalogOptions = {
provide: CATALOG_OPTIONS,
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
pageLimit: parsePageLimit(config.get("CATALOG_PAGE_LIMIT"))
}),
};
Tokens, ConfigService and parsePageLimit are assumed. This factory describes injection only and is not compiled or executed.
Environment text needs a typed runtime contract
An environment value is text even when it looks like a number or Boolean. A string false is truthy in JavaScript, so Boolean(value) is not a safe generic configuration parser. Define syntax, range, defaults and required fields explicitly. Distinguish an omitted optional setting from an invalid supplied value rather than silently falling back on every error. Validate before the application advertises readiness, and report setting names without printing credential values. Dynamic modules and namespaced configuration can organize composition, but they do not automatically validate every value returned by a custom configuration factory.
Parse one bounded setting locally
The complete parser accepts a positive decimal limit up to fifty and supplies ten only when the setting is absent. Empty text, partial numbers and excessive values throw. The thresholds are synthetic choices for this training catalog, not universal tuning advice. A real configuration adapter should use the same contract for every deployment source and preserve the parsed number as an immutable option. This model reads no actual environment variable, opens no file and constructs no provider. Its narrow evidence is that omission and invalid input follow distinct reproducible branches.
function parseLimit(text) {
if (text === undefined) return 10;
if (typeof text !== "string" || !/^[1-9][0-9]*$/.test(text)) throw new Error("INVALID_LIMIT");
const n=Number(text); if (!Number.isSafeInteger(n) || n > 50) throw new Error("INVALID_LIMIT");
return n;
}
console.assert(parseLimit(undefined) === 10);
console.assert(parseLimit("20") === 20);
let rejected=false; try {parseLimit("20rows");} catch {rejected=true;}
console.assert(rejected);
Absence defaults to ten, valid text becomes twenty and the partial number is rejected. No process environment is accessed.
Separate construction failure from unknown dependency
A factory that rejects after resolving its inputs has a different fault from a missing token. Preserve that stage in diagnostics and avoid making every module global to silence startup failures. Dependency initialization and resource cleanup need an owner, including partial startup when one source succeeds and another fails. Close already-owned resources under the chosen lifecycle protocol rather than leaving a half-ready worker. Verify async construction with controlled fakes in an authorized testing module. This course executes only the parser, so it does not claim Nest awaited a factory, loaded a secret or connected to a source.
Common errors and fixes
The string false enables a feature
- Why it happens
- Truthiness replaced explicit configuration parsing.
- How to fix it
- Parse a documented Boolean vocabulary.
- Prevent it next time
- Test absent, false, true and invalid text.
Two alias tokens create two clients
- Why it happens
- useClass was used where shared useExisting identity was intended.
- How to fix it
- Bind the intended existing provider and review ownership.
- Prevent it next time
- Verify instance identity in a focused container test.
Startup hangs while readiness appears healthy
- Why it happens
- Async construction lacked a bounded completion contract.
- How to fix it
- Bound initialization and advertise readiness only after prerequisites.
- Prevent it next time
- Test rejection and partial-start cleanup.
Check your understanding
1. Why is an invalid supplied limit different from an omitted one?
Show worked solution
Omission can legitimately select a documented default, while invalid text signals a deployment mistake. Silently defaulting both can hide intended limits and make the service behave differently from its reviewed configuration. Keep explicit validation and safe setting-name diagnostics, then test the adapter that supplies the raw value.
2. Does an async factory make its source resilient?
Show worked solution
No. It lets dependency construction await a result. Deadlines, retries, cleanup and ongoing availability remain separate contracts. Test those with controlled adapters and avoid a factory that retries forever while the process appears ready. The local parser cannot establish any network or lifecycle guarantee.
Key takeaways
- Provider forms carry identity and lifecycle meaning.
- Validate configuration before readiness and keep secrets out of diagnostics.