Files
What you'll learn
- Provide a class Nest cannot construct through useFactory, with other providers handed in through inject
- Alias one instance under a second token with useExisting, and know why useClass there would be a second instance
- Choose an implementation at startup with a useClass expression, and mark an injected token optional
Factory and Alias Providers
useClass and useValue cover a class Nest can construct on its own and an object that already exists. Two situations fall between them. A class whose constructor takes something that is not a provider, a port number or a capacity read from configuration, cannot be built by Nest and is not built yet either. And an instance that must be reachable under two tokens, because two parts of the application know it by different names, must not become two instances. The remaining two recipes, useFactory and useExisting, are for exactly those.
Choosing a class at startup
useClass takes any expression, so the class can be chosen when the module is evaluated:
const mailerProvider = {
provide: Mailer,
useClass: process.env.NODE_ENV === 'production' ? SmtpMailer : ConsoleMailer,
};
@Module({
providers: [mailerProvider],
})
export class NotificationsModule {}
Everything asking for Mailer gets whichever was chosen, and nothing else changes. Provider objects are often declared as constants like this and listed by name, which keeps a module's decorator readable when the recipes grow.
Building with a factory
useFactory is a function whose return value becomes the provider. Nest calls it once, when the module initialises, and it can ask for other providers by listing their tokens in inject. They arrive as the function's arguments, in the same order:
const connectionProvider = {
provide: 'CONNECTION',
useFactory: (options: DatabaseOptions, logger?: Logger) => {
return new DatabaseConnection(options.url, logger);
},
inject: [DATABASE_OPTIONS, { token: Logger, optional: true }],
};
@Module({
providers: [connectionProvider, { provide: DATABASE_OPTIONS, useValue: { url: 'sqlite::memory:' } }],
})
export class DatabaseModule {}
Three things to notice. DatabaseConnection has no @Injectable() and needs none: the factory constructs it, so Nest never inspects its constructor. The factory's parameters are typed by hand, because inject is what decides what arrives, not the types. And an entry written as { token, optional: true } arrives as undefined when nothing provides it instead of failing the boot.
A factory can return anything, not only a class instance. A plain configuration object computed at startup is a common provider:
const configProvider = {
provide: 'CONFIG',
useFactory: () => (process.env.NODE_ENV === 'development' ? devConfig : prodConfig),
};
One instance, two names
useExisting makes a token an alias for another token. The instance is looked up, not created, so both tokens resolve to the same object:
@Module({
providers: [
LoggerService,
{ provide: 'AliasedLoggerService', useExisting: LoggerService },
],
})
export class LoggingModule {}
Aliases exist for migrations and for boundaries: old code keeps its string token while new code injects the class, or a module exposes a stable name so importers do not depend on a class that may be renamed. Whichever token a constructor uses, there is one instance.
Exporting a factory
A factory provider is exported like any other, by token or as the whole object:
@Module({
providers: [connectionProvider],
exports: ['CONNECTION'],
})
export class DatabaseModule {}
Your task
The cats now live in a CatsRepository whose constructor takes a capacity, a plain number, so Nest cannot build it. StatsController, in the root module, counts cats through a token named CATS_STORE.
- Provide
CatsRepositorythrough a factory that builds it from the configuration underCATS_CONFIG: the capacity is the config'smaxCats. - Make
CATS_STOREa second name for that same repository, and share it, so the count inGET /statsis the count of the catsPOST /catsstored.
The tests create cats past the capacity and read the stats between creations. If the count is always zero, two repositories exist.
When it fails
Nest can't resolve dependencies of the CatsService (?, Logger): nothing providesCatsRepositoryyet. Its class is the token; the factory is the recipe.Nest can't resolve dependencies of the CatsRepository (?): the factory'sinjectlist is missing an entry, or lists a token nothing provides. Nest names the factory by the token it builds.- The fourth cat is accepted: the factory received
undefinedfor the config, so the capacity isundefinedand no comparison ever fails. Checkinject. GET /statscounts 0 whileGET /catslists cats:CATS_STOREwas provided withuseFactoryoruseClassrather thanuseExisting, so it is a second repository.Nest can't resolve dependencies of the StatsController (?): the alias is not exported.
Remember
useFactorybuilds a provider from a function;injectlists the tokens whose instances the function receives, in order.- A factory-built class needs no
@Injectable(); Nest never looks at its constructor. useExistingis an alias: two tokens, one instance.- Mark an
injectentryoptional: trueto receiveundefinedinstead of a failed boot.
Stuck? Show a hint
A provider object with provide: CatsRepository, a useFactory taking the config and returning new CatsRepository(config.maxCats), and inject: [CATS_CONFIG]. Then { provide: 'CATS_STORE', useExisting: CatsRepository }, and 'CATS_STORE' in exports.
Press Run tests to start the app. Its log appears here.Graded endpoints
The repository exists, built by the factory, so the service boots and stores a cat
Second cat
The service lists through the same repository
The alias resolves to the same repository the service writes to, and the capacity came from the config through inject
Third of three
The factory gave the repository its capacity from the config, so the fourth is refused
Still one repository: the alias counts three