Custom Providers
Most of the time you register a provider by just listing its class, and Nest infers everything it needs. But the dependency injection container is far more flexible than that shorthand suggests. A custom provider lets you decouple the token consumers inject by from the value Nest actually supplies — so you can inject constants, swap implementations, build values dynamically, or alias one provider under another name. Nest exposes four recipes for this: useValue, useClass, useFactory, and useExisting.
The standard provider, expanded
When you write providers: [CatsService], Nest expands it into a full provider object. Understanding that expanded form is the key to every custom provider, because the custom recipes simply change which use* key you supply.
// these two registrations are identical
providers: [CatsService];
providers: [
{
provide: CatsService, // the injection token
useClass: CatsService, // what to instantiate for it
},
];
The provide key is the token — the identity consumers ask for in their constructors. The use* key tells the container how to produce the value bound to that token. Below are the four recipes, each solving a different problem.
useValue — supply a constant or mock
useValue binds a token to a ready-made value: a configuration object, a third-party client you constructed yourself, or a mock in a test. Nest performs no instantiation; it just hands back exactly what you gave it.
// app.module.ts
import { Module } from '@nestjs/common';
const appConfig = {
apiUrl: 'https://api.devcraftly.com',
timeoutMs: 5000,
};
@Module({
providers: [
{
provide: 'APP_CONFIG',
useValue: appConfig,
},
],
})
export class AppModule {}
Because the token here is a string, consumers inject it with the @Inject() decorator rather than by type:
import { Inject, Injectable } from '@nestjs/common';
@Injectable()
export class HttpClient {
constructor(@Inject('APP_CONFIG') private readonly config: { apiUrl: string; timeoutMs: number }) {}
baseUrl(): string {
return this.config.apiUrl;
}
}
useValue shines in tests, where you replace a real service with a stub that has the same shape:
const moduleRef = await Test.createTestingModule({
providers: [
CatsController,
{ provide: CatsService, useValue: { findAll: () => [{ id: 1, name: 'Misty' }] } },
],
}).compile();
useClass — swap implementations per environment
useClass keeps a stable token but lets you choose the concrete class at registration time. This is the canonical way to inject against an abstraction and resolve a different implementation depending on configuration or environment.
// config.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export abstract class ConfigService {
abstract get(key: string): string | undefined;
}
@Injectable()
export class DevConfigService extends ConfigService {
get(key: string) {
return process.env[key] ?? 'dev-default';
}
}
@Injectable()
export class ProdConfigService extends ConfigService {
get(key: string) {
return process.env[key];
}
}
// app.module.ts
@Module({
providers: [
{
provide: ConfigService,
useClass: process.env.NODE_ENV === 'production' ? ProdConfigService : DevConfigService,
},
],
})
export class AppModule {}
Consumers always inject ConfigService; they never know or care which subclass they received.
Tip: Unlike
useValue, a class supplied withuseClassis instantiated by Nest, so its own constructor dependencies are resolved from the container as usual. You get a fully wired instance, not a bare object.
useFactory — build a value dynamically
useFactory registers a function whose return value becomes the provider. The factory can itself receive injected dependencies through the inject array, and it may be async — making it the recipe of choice for values that require setup, computation, or other providers.
// database.module.ts
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
@Module({
providers: [
{
provide: 'DB_CONNECTION',
useFactory: async (config: ConfigService) => {
const url = config.get('DATABASE_URL');
const connection = await createConnection(url);
return connection;
},
inject: [ConfigService], // resolved and passed positionally to the factory
},
],
exports: ['DB_CONNECTION'],
})
export class DatabaseModule {}
The entries in inject are resolved by the container and passed to the factory in order. Because the factory runs once and its result is cached, the connection above is created a single time and shared as a singleton.
useExisting — create an alias
useExisting makes one token an alias for another already-registered provider. No new instance is created; both tokens resolve to the same object. This is useful for exposing a service under a new name without duplicating it.
@Module({
providers: [
LoggerService,
{
provide: 'AliasedLogger',
useExisting: LoggerService, // resolves to the SAME instance
},
],
})
export class AppModule {}
Injecting @Inject('AliasedLogger') and injecting LoggerService directly yield the identical singleton — handy when migrating a token name or offering a friendlier alias to consumers.
Recipe comparison
| Recipe | What it binds | Instance created by | Common use |
|---|---|---|---|
useValue | A literal value or object | You | Config, constants, test mocks |
useClass | A class chosen at registration time | Nest | Swapping implementations |
useFactory | A function’s return value | Your factory | Dynamic/async construction with deps |
useExisting | An alias to another token | (reused) | Renaming or aliasing a provider |
Output:
$ curl -s localhost:3000/health
{"db":"connected","config":"DevConfigService","logger":"shared-singleton"}
Best practices
- Pick the narrowest recipe: prefer the default class shorthand, and reach for custom providers only when you genuinely need to decouple token from value.
- Use string or symbol tokens together with
@Inject()for non-class values like config objects and connections; symbols avoid collisions. - Favor
useClassover branching inside a service when you need different implementations per environment. - Use
useFactorywith theinjectarray for any value that needs other providers or async setup; keep the factory body small and side-effect-aware. - Reach for
useExistingto alias rather than re-registering a class withuseClass, which would create a second independent instance. - Centralize non-trivial provider definitions (especially factories) in a dedicated module and
exportthe tokens other modules need.