Careeroza
Custom Providers
The Long Form of a Provider
providers: [ProductsService] is short for { provide: ProductsService, useClass: ProductsService }. The long form lets you control what gets injected.
| Form | Injects | Typical use |
|---|---|---|
useClass | A new instance of a class | Swap implementation (mock, different gateway) |
useValue | A fixed value or object | Constants, config objects, test mocks |
useFactory | Whatever a function returns (can be async) | Build a client from config |
useExisting | An alias for another provider | Two tokens, one instance |
All Four in One Module
src/payments/payments.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { PaymentGateway, RazorpayGateway, FakeGateway } from './gateways';
export const GST_RATE = 'GST_RATE';
export const RAZORPAY_CLIENT = 'RAZORPAY_CLIENT';
@Module({
imports: [ConfigModule],
providers: [
{ provide: GST_RATE, useValue: 0.18 },
{
provide: PaymentGateway,
useClass: process.env.NODE_ENV === 'test' ? FakeGateway : RazorpayGateway,
},
{
provide: RAZORPAY_CLIENT,
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
keyId: config.getOrThrow<string>('RAZORPAY_KEY_ID'),
keySecret: config.getOrThrow<string>('RAZORPAY_KEY_SECRET'),
}),
},
{ provide: 'GATEWAY', useExisting: PaymentGateway },
],
exports: [PaymentGateway],
})
export class PaymentsModule {}Simulating the Four Forms
Example
A small registry that resolves the four provider forms the way NestJS does:
type Token = string;
type Provider =
| { provide: Token; useValue: unknown }
| { provide: Token; useClass: new () => unknown }
| { provide: Token; useFactory: (...deps: any[]) => unknown; inject?: Token[] }
| { provide: Token; useExisting: Token };
class Injector {
private cache = new Map<Token, unknown>();
constructor(private providers: Provider[]) {}
get(token: Token): unknown {
if (this.cache.has(token)) return this.cache.get(token);
const p = this.providers.find((x) => x.provide === token);
if (!p) throw new Error('No provider for ' + token);
let value: unknown;
if ('useValue' in p) value = p.useValue;
else if ('useClass' in p) value = new p.useClass();
else if ('useFactory' in p) value = p.useFactory(...(p.inject ?? []).map((t) => this.get(t)));
else value = this.get(p.useExisting);
this.cache.set(token, value);
return value;
}
}
class RazorpayGateway { name = 'razorpay'; }
const injector = new Injector([
{ provide: 'ENV', useValue: { RAZORPAY_KEY_ID: 'rzp_test_123' } },
{ provide: 'GATEWAY', useClass: RazorpayGateway },
{ provide: 'CLIENT', inject: ['ENV'], useFactory: (env: { RAZORPAY_KEY_ID: string }) => ({ key: env.RAZORPAY_KEY_ID }) },
{ provide: 'PAYMENTS', useExisting: 'GATEWAY' },
]);
console.log(injector.get('GATEWAY'));
console.log(injector.get('CLIENT'));
console.log('alias is same instance:', injector.get('PAYMENTS') === injector.get('GATEWAY'));Output
RazorpayGateway { name: 'razorpay' }
{ key: 'rzp_test_123' }
alias is same instance: true