Not everything is a class
Config objects, API clients and feature flags aren't services you write — but you still want them injected. Custom providers register *any* value under an injection token, and @Inject(token) asks for it.
Three kinds of custom provider
import { Controller, Get, Inject, Injectable, Module } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
export const CONFIG = "CONFIG";
@Injectable()
class PricingService {
constructor(@Inject(CONFIG) private readonly config: { taxRate: number; currency: string }) {}
withTax(price: number) {
return `${this.config.currency} ${Math.round(price * (1 + this.config.taxRate))}`;
}
}
@Controller()
class PricesController {
constructor(private readonly pricing: PricingService, @Inject("OPENED_AT") private readonly openedAt: string) {}
@Get("momo")
momo() {
return { price: this.pricing.withTax(180), openedAt: this.openedAt };
}
}
@Module({
controllers: [PricesController],
providers: [
PricingService,
{ provide: CONFIG, useValue: { taxRate: 0.13, currency: "Rs" } },
{ provide: "OPENED_AT", useFactory: (cfg: { currency: string }) => `opened with ${cfg.currency}`, inject: [CONFIG] },
],
})
class AppModule {}
const app = await NestFactory.create(AppModule);
await app.listen(3000);
console.log(await (await fetch("http://localhost:3000/momo")).json());- useClass swaps an implementation: { provide: PaymentsService, useClass: FakePaymentsService } in development.
- Real apps use @nestjs/config (ConfigModule.forRoot(), ConfigService.get("PORT")) — built on exactly these providers.