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

RecipeWhat it hands overReach for it when
useClassA fresh instance of a classYou want to swap the implementation behind a token (real vs. fake, prod vs. dev).
useValueA ready-made object or primitiveConfig objects, constants, or a mock in tests.
useFactoryWhatever your function returnsConstruction needs logic or other injected providers.
useExistingAn alias to an already-registered tokenTwo 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

Example · typescript
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.

Example · ts
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.

Example · ts
{
  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.

Example · ts
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

  • Be the first to comment on this lesson.