Modules and Dependency Injection

Explain imports, exports and providers in @Module.

providers are created in the module and are private to it by default. exports makes some of them available to modules that import this module. imports lists modules whose exported providers this module needs.

So to use ProductsService inside OrdersService: ProductsModule exports it, OrdersModule imports ProductsModule. Re-listing ProductsService in OrdersModule's providers would create a second instance, which is a common bug.

What are custom providers? Give an example of each type.

@Module({
  providers: [
    { provide: 'GST_RATE', useValue: 0.18 },                            // constant / mock
    { provide: PaymentGateway, useClass: RazorpayGateway },             // swap implementation
    { provide: 'REDIS', inject: [ConfigService],                        // built from config, may be async
      useFactory: (c: ConfigService) => new Redis(c.getOrThrow('REDIS_URL')) },
    { provide: 'GATEWAY_ALIAS', useExisting: PaymentGateway },          // alias, same instance
  ],
})
export class PaymentsModule {}

What are provider scopes and what is the cost of REQUEST scope?

ScopeLifetime
DEFAULTOne singleton per app (recommended)
REQUESTNew instance per incoming request
TRANSIENTNew instance for each consumer that injects it

REQUEST scope bubbles up the injection chain: every provider and controller that depends on a request-scoped provider also becomes request-scoped and is re-created per request. That adds allocation and latency, so prefer passing request data as method arguments, or use AsyncLocalStorage (e.g. nestjs-cls).

How do you handle a circular dependency between two services?

Short term: forwardRef(() => OtherModule) in both modules' imports and @Inject(forwardRef(() => OtherService)) in the constructor. Better: remove the cycle. Extract shared logic into a third service, or decouple with events (@nestjs/event-emitter): PaymentsService emits payment.captured and OrdersService listens, so neither imports the other.

What is a dynamic module and what do forRoot, forFeature and register mean?

A dynamic module is returned by a static method and configured at import time: { module, providers, exports, global? }. By convention forRoot() / forRootAsync() configures it once for the app (DB connection, config), forFeature() adds per-module pieces (entities/repositories for this module), and register() configures it per importing module (e.g. JwtModule.register()). The Async variants accept useFactory + inject to read config.