Skip to content
NestJS ns providers 5 min read

Custom Providers

Most of the time you register a provider by just listing its class, and Nest infers everything it needs. But the dependency injection container is far more flexible than that shorthand suggests. A custom provider lets you decouple the token consumers inject by from the value Nest actually supplies — so you can inject constants, swap implementations, build values dynamically, or alias one provider under another name. Nest exposes four recipes for this: useValue, useClass, useFactory, and useExisting.

The standard provider, expanded

When you write providers: [CatsService], Nest expands it into a full provider object. Understanding that expanded form is the key to every custom provider, because the custom recipes simply change which use* key you supply.

// these two registrations are identical
providers: [CatsService];

providers: [
  {
    provide: CatsService, // the injection token
    useClass: CatsService, // what to instantiate for it
  },
];

The provide key is the token — the identity consumers ask for in their constructors. The use* key tells the container how to produce the value bound to that token. Below are the four recipes, each solving a different problem.

useValue — supply a constant or mock

useValue binds a token to a ready-made value: a configuration object, a third-party client you constructed yourself, or a mock in a test. Nest performs no instantiation; it just hands back exactly what you gave it.

// app.module.ts
import { Module } from '@nestjs/common';

const appConfig = {
  apiUrl: 'https://api.devcraftly.com',
  timeoutMs: 5000,
};

@Module({
  providers: [
    {
      provide: 'APP_CONFIG',
      useValue: appConfig,
    },
  ],
})
export class AppModule {}

Because the token here is a string, consumers inject it with the @Inject() decorator rather than by type:

import { Inject, Injectable } from '@nestjs/common';

@Injectable()
export class HttpClient {
  constructor(@Inject('APP_CONFIG') private readonly config: { apiUrl: string; timeoutMs: number }) {}

  baseUrl(): string {
    return this.config.apiUrl;
  }
}

useValue shines in tests, where you replace a real service with a stub that has the same shape:

const moduleRef = await Test.createTestingModule({
  providers: [
    CatsController,
    { provide: CatsService, useValue: { findAll: () => [{ id: 1, name: 'Misty' }] } },
  ],
}).compile();

useClass — swap implementations per environment

useClass keeps a stable token but lets you choose the concrete class at registration time. This is the canonical way to inject against an abstraction and resolve a different implementation depending on configuration or environment.

// config.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export abstract class ConfigService {
  abstract get(key: string): string | undefined;
}

@Injectable()
export class DevConfigService extends ConfigService {
  get(key: string) {
    return process.env[key] ?? 'dev-default';
  }
}

@Injectable()
export class ProdConfigService extends ConfigService {
  get(key: string) {
    return process.env[key];
  }
}
// app.module.ts
@Module({
  providers: [
    {
      provide: ConfigService,
      useClass: process.env.NODE_ENV === 'production' ? ProdConfigService : DevConfigService,
    },
  ],
})
export class AppModule {}

Consumers always inject ConfigService; they never know or care which subclass they received.

Tip: Unlike useValue, a class supplied with useClass is instantiated by Nest, so its own constructor dependencies are resolved from the container as usual. You get a fully wired instance, not a bare object.

useFactory — build a value dynamically

useFactory registers a function whose return value becomes the provider. The factory can itself receive injected dependencies through the inject array, and it may be async — making it the recipe of choice for values that require setup, computation, or other providers.

// database.module.ts
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';

@Module({
  providers: [
    {
      provide: 'DB_CONNECTION',
      useFactory: async (config: ConfigService) => {
        const url = config.get('DATABASE_URL');
        const connection = await createConnection(url);
        return connection;
      },
      inject: [ConfigService], // resolved and passed positionally to the factory
    },
  ],
  exports: ['DB_CONNECTION'],
})
export class DatabaseModule {}

The entries in inject are resolved by the container and passed to the factory in order. Because the factory runs once and its result is cached, the connection above is created a single time and shared as a singleton.

useExisting — create an alias

useExisting makes one token an alias for another already-registered provider. No new instance is created; both tokens resolve to the same object. This is useful for exposing a service under a new name without duplicating it.

@Module({
  providers: [
    LoggerService,
    {
      provide: 'AliasedLogger',
      useExisting: LoggerService, // resolves to the SAME instance
    },
  ],
})
export class AppModule {}

Injecting @Inject('AliasedLogger') and injecting LoggerService directly yield the identical singleton — handy when migrating a token name or offering a friendlier alias to consumers.

Recipe comparison

RecipeWhat it bindsInstance created byCommon use
useValueA literal value or objectYouConfig, constants, test mocks
useClassA class chosen at registration timeNestSwapping implementations
useFactoryA function’s return valueYour factoryDynamic/async construction with deps
useExistingAn alias to another token(reused)Renaming or aliasing a provider

Output:

$ curl -s localhost:3000/health
{"db":"connected","config":"DevConfigService","logger":"shared-singleton"}

Best practices

  • Pick the narrowest recipe: prefer the default class shorthand, and reach for custom providers only when you genuinely need to decouple token from value.
  • Use string or symbol tokens together with @Inject() for non-class values like config objects and connections; symbols avoid collisions.
  • Favor useClass over branching inside a service when you need different implementations per environment.
  • Use useFactory with the inject array for any value that needs other providers or async setup; keep the factory body small and side-effect-aware.
  • Reach for useExisting to alias rather than re-registering a class with useClass, which would create a second independent instance.
  • Centralize non-trivial provider definitions (especially factories) in a dedicated module and export the tokens other modules need.
Last updated June 14, 2026
Was this helpful?