Factory & Custom Provider Pattern
NestJS resolves most providers by simply instantiating a class, but real applications often need dependencies whose shape depends on runtime configuration, asynchronous setup, or a choice between several implementations. Custom providers — and the factory recipe in particular — give you full control over how a token is constructed and what it resolves to. This page shows how to build configurable dependencies with useFactory, implement the abstract factory pattern, and pick the right provider recipe for the job.
Why custom providers exist
The shorthand providers: [CatsService] is sugar for the class provider recipe: Nest reads the constructor metadata, resolves each dependency, and calls new CatsService(...). That works until you need something Nest cannot infer — a value computed from environment variables, a third-party client created via its own factory function, or a different implementation selected per environment. Custom providers expose four recipes, each keyed by a token.
| Recipe | Key | Resolves to | Use when |
|---|---|---|---|
| Class | useClass | A class instance Nest constructs | You want to swap the concrete class behind a token |
| Value | useValue | A literal value or pre-built object | Injecting constants, mocks, or external instances |
| Factory | useFactory | The return value of a function | Construction needs logic, config, or async work |
| Alias | useExisting | An already-registered provider | Aliasing one token to another |
Building a dependency from runtime config
The useFactory recipe runs a function whose parameters are themselves injected via the inject array. This makes it the natural place to read configuration and assemble a client. Below, a database connection is built from ConfigService values rather than hardcoded.
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { createPool, Pool } from 'mysql2/promise';
export const DATABASE_POOL = 'DATABASE_POOL';
@Module({
imports: [ConfigModule],
providers: [
{
provide: DATABASE_POOL,
inject: [ConfigService],
useFactory: (config: ConfigService): Pool => {
return createPool({
host: config.get<string>('DB_HOST', 'localhost'),
port: config.get<number>('DB_PORT', 3306),
user: config.get<string>('DB_USER'),
password: config.get<string>('DB_PASSWORD'),
database: config.get<string>('DB_NAME'),
connectionLimit: config.get<number>('DB_POOL_SIZE', 10),
});
},
},
],
exports: [DATABASE_POOL],
})
export class DatabaseModule {}
Because DATABASE_POOL is a string token, consumers inject it with @Inject:
import { Inject, Injectable } from '@nestjs/common';
import { Pool } from 'mysql2/promise';
import { DATABASE_POOL } from './database.module';
@Injectable()
export class UserRepository {
constructor(@Inject(DATABASE_POOL) private readonly pool: Pool) {}
async findById(id: number) {
const [rows] = await this.pool.query('SELECT * FROM users WHERE id = ?', [id]);
return (rows as unknown[])[0];
}
}
Tip: Prefer a typed
InjectionTokenconstant (aconstexported from a shared file) over inline magic strings. It keeps theprovideand@Injectsides in sync and makes refactors safe.
Async factories
A factory may be async, which lets you await connections, secrets, or feature flags before the provider becomes available. Nest holds initialization until every async factory resolves, so anything depending on the token sees a ready instance.
{
provide: 'REDIS_CLIENT',
inject: [ConfigService],
useFactory: async (config: ConfigService) => {
const { createClient } = await import('redis');
const client = createClient({ url: config.get<string>('REDIS_URL') });
await client.connect();
return client;
},
}
Implementing the abstract factory pattern
When the dependency you need is itself chosen at runtime, expose an abstract class as the token and bind a factory that returns the right concrete implementation. Abstract classes are ideal tokens because they exist at runtime (unlike interfaces) yet enforce a contract.
// payment-gateway.ts
export abstract class PaymentGateway {
abstract charge(amountCents: number, currency: string): Promise<string>;
}
// stripe.gateway.ts
import { Injectable } from '@nestjs/common';
import { PaymentGateway } from './payment-gateway';
@Injectable()
export class StripeGateway extends PaymentGateway {
async charge(amountCents: number, currency: string): Promise<string> {
return `stripe_txn_${currency}_${amountCents}`;
}
}
// paypal.gateway.ts
import { Injectable } from '@nestjs/common';
import { PaymentGateway } from './payment-gateway';
@Injectable()
export class PaypalGateway extends PaymentGateway {
async charge(amountCents: number, currency: string): Promise<string> {
return `paypal_txn_${currency}_${amountCents}`;
}
}
The module wires a factory that selects an implementation based on configuration:
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { PaymentGateway } from './payment-gateway';
import { StripeGateway } from './stripe.gateway';
import { PaypalGateway } from './paypal.gateway';
@Module({
imports: [ConfigModule],
providers: [
StripeGateway,
PaypalGateway,
{
provide: PaymentGateway,
inject: [ConfigService, StripeGateway, PaypalGateway],
useFactory: (
config: ConfigService,
stripe: StripeGateway,
paypal: PaypalGateway,
): PaymentGateway => {
return config.get('PAYMENT_PROVIDER') === 'paypal' ? paypal : stripe;
},
},
],
exports: [PaymentGateway],
})
export class PaymentModule {}
Consumers depend only on the abstraction, so swapping providers never touches business code:
@Injectable()
export class CheckoutService {
constructor(private readonly gateway: PaymentGateway) {}
pay(cents: number) {
return this.gateway.charge(cents, 'usd');
}
}
Output:
PAYMENT_PROVIDER=stripe -> stripe_txn_usd_4999
PAYMENT_PROVIDER=paypal -> paypal_txn_usd_4999
Choosing between recipes
Reach for the simplest recipe that expresses intent. Use useValue for constants and test doubles, useClass when only the concrete type varies, useExisting to alias tokens, and useFactory whenever construction needs logic — config lookups, conditional selection, or async setup. If a factory grows large, extract it into a named function so the module stays readable and the factory is unit-testable in isolation.
Best practices
- Export a typed token constant instead of repeating magic strings across modules.
- Prefer abstract classes over interfaces as tokens so the contract survives at runtime.
- Keep factory bodies focused; extract complex assembly into a standalone, testable function.
- List every dependency in
injectin the same order as the factory parameters — mismatches surface as confusing runtime errors. - Make factories
asynconly when you genuinely await; unnecessary promises delay app bootstrap. - Always
exportsthe token if other modules consume it, and import the providing module there. - Bind concrete implementations as real providers when an abstract-factory needs to inject them.