The Four Provider Recipes
Master useClass, useValue, useFactory and useExisting to control exactly what the DI container hands to your classes.
Once you outgrow providers: [MyService], every wiring problem you meet is really a question of how the container should build a token. Nest gives you four recipes, and a senior engineer reaches for them by reflex.
The four forms at a glance
| Recipe | What it hands over | Reach for it when |
|---|---|---|
useClass | A fresh instance of a class | You want to swap the implementation behind a token (real vs. fake, prod vs. dev). |
useValue | A ready-made object or primitive | Config objects, constants, or a mock in tests. |
useFactory | Whatever your function returns | Construction needs logic or other injected providers. |
useExisting | An alias to an already-registered token | Two names should resolve to the same singleton. |
The simplest one first
useValue is the gateway drug. You hand Nest an object and it injects that exact reference wherever the token is requested:
const CONFIG = 'CONFIG';
@Module({
providers: [{ provide: CONFIG, useValue: { retries: 3 } }],
})
export class CoreModule {}
// consume it with a matching token
constructor(@Inject(CONFIG) private cfg: { retries: number }) {}useExisting is the one people miss
useExisting creates an alias. Both tokens resolve to one shared instance, so you can expose a narrow interface token publicly while keeping the concrete class internal. It is not a second instance, it is the same object under a second name.
Non-class tokens need @Inject
When the token is a string or a Symbol, TypeScript can no longer infer it from the parameter type. Tell Nest explicitly with @Inject('TOKEN'). A common senior habit is to export a small const or InjectionToken-style symbol so the token can never be mistyped.
Example
import { Module, Inject, Injectable } from '@nestjs/common';
// A public contract, kept separate from any concrete class.
export interface PaymentGateway {
charge(cents: number): Promise<string>;
}
export const PAYMENT_GATEWAY = Symbol('PAYMENT_GATEWAY');
@Injectable()
class StripeGateway implements PaymentGateway {
async charge(cents: number) {
return `stripe:${cents}`;
}
}
@Injectable()
class FakeGateway implements PaymentGateway {
async charge(cents: number) {
return `fake:${cents}`;
}
}
@Module({
providers: [
StripeGateway,
FakeGateway,
{
// useClass: pick an implementation per environment
provide: PAYMENT_GATEWAY,
useClass: process.env.NODE_ENV === 'test' ? FakeGateway : StripeGateway,
},
{
// useFactory: build with logic + injected deps
provide: 'RETRYING_GATEWAY',
useFactory: (gw: PaymentGateway) => ({
charge: (c: number) => gw.charge(c).catch(() => gw.charge(c)),
}),
inject: [PAYMENT_GATEWAY],
},
{
// useExisting: a second name for the SAME singleton
provide: 'GATEWAY_ALIAS',
useExisting: PAYMENT_GATEWAY,
},
],
exports: [PAYMENT_GATEWAY, 'RETRYING_GATEWAY'],
})
export class PaymentsModule {}
@Injectable()
export class CheckoutService {
constructor(
@Inject(PAYMENT_GATEWAY) private readonly gateway: PaymentGateway,
) {}
pay(cents: number) {
return this.gateway.charge(cents);
}
}When to use it
- A developer uses useExisting to alias an AxiosHttpService as the HTTP_CLIENT token so legacy code keeps working after the class rename.
- A team uses useFactory with async to connect to Redis before the app starts, injecting the live client into all dependent services.
- An architect uses useClass to swap the real PaymentGateway for a FakePaymentGateway in a testing environment without changing any injection site.
More examples
All four recipes side by side
Places all four provider recipes in one providers array for a direct comparison of their syntax and use cases.
providers: [
// 1. useClass — swap implementation
{ provide: LoggerService, useClass: PinoLoggerService },
// 2. useValue — inject a constant
{ provide: 'CONFIG', useValue: { timeout: 5000 } },
// 3. useFactory — async setup
{ provide: 'REDIS', inject: [ConfigService],
useFactory: async (cfg: ConfigService) =>
createClient({ url: cfg.get('REDIS_URL') }),
},
// 4. useExisting — alias
{ provide: 'LOGGER', useExisting: LoggerService },
]Async factory with multiple deps
Demonstrates a useFactory provider that injects two dependencies, performs async verification, and logs before returning the live transport.
{
provide: 'MAILER',
inject: [ConfigService, LoggerService],
useFactory: async (cfg: ConfigService, logger: LoggerService) => {
const transport = nodemailer.createTransport({ host: cfg.get('SMTP_HOST') });
await transport.verify();
logger.log('SMTP connection verified');
return transport;
},
}useExisting alias token
Creates a string-token alias for an existing class provider so legacy injection sites keep working after a refactor.
import { Injectable } from '@nestjs/common';
@Injectable()
export class HttpService { get(url: string) { /* ... */ } }
// Alias — both tokens resolve to the same singleton
providers: [
HttpService,
{ provide: 'HTTP_CLIENT', useExisting: HttpService },
]
// Legacy service still works:
constructor(@Inject('HTTP_CLIENT') private http: HttpService) {}
Discussion