Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

NestJS  Module 3 – Providers and dependency injection

Custom providers, tokens and factories

How useValue, useClass, useFactory and useExisting give Nest providers that are not plain classes, and how symbol and string tokens are injected.

  • Intermediate
  • 15 minutes
  • Examples run with Node.js 24.21.0, NestJS 12.1.2, TypeScript 6.0.3 and quickjs 0.32.0
  • By MySmartCoPilot
Free core, Pro depth: 5 of 15 minutes free
Free: A provider entry pairs a token with a recipe · Symbols, strings and classes as tokens. With a pass: 2 in-depth parts and 3 interview answers, about 10 minutes.

What you will learn

  • Configure values, classes, factories and aliases as providers with useValue, useClass, useFactory and useExisting
  • Use symbol and string tokens with @Inject(), and choose symbols so that tokens cannot collide
  • Write async factory providers with optional dependencies and a deadline on start-up
  • Check which instance an alias or a test override resolves to, on the Nest version you run

Before you start

What you’ll learn · Before you start

What you will learn

  • Configure values, classes, factories and aliases as providers with useValue, useClass, useFactory and useExisting
  • Use symbol and string tokens with @Inject(), and choose symbols so that tokens cannot collide
  • Write async factory providers with optional dependencies and a deadline on start-up
  • Check which instance an alias or a test override resolves to, on the Nest version you run

Before you start

On this page

TiffinTrail’s lunch reminder needs four things that are not plain classes: its settings, which are a plain object; an SMS gateway that is fake in tests and live in production; a reminder built from those two; and a second name for the gateway that older code asks for. Constructor types cannot express any of the four.

custom provider: A providers entry written out as { provide: token, … } with one recipe, useValue, useClass, useFactory or useExisting, instead of a bare class. It lets a token stand for a value, a chosen class or what a function returns. can. Each entry in providers pairs a token with a recipe that says what Nest should inject for it, and a token that is not a class is injected by name with @Inject(). This lesson runs all four recipes in one module, then shows how factory provider: A custom provider with useFactory: its value is what a function returns. Nest calls the function once, passing the providers listed in inject in that order. hold start-up until a connection is ready, and where tokens and test overrides surprise people.

A provider entry pairs a token with a recipe

The bare class in providers: [WalletService] is short for { provide: WalletService, useClass: WalletService }. Written out, every entry has a token (provide) and one of four recipes:

  • useValue injects one existing object exactly as written, such as settings or a test double.
  • useClass makes Nest build a class for the token, a class you can choose per environment.
  • useFactory injects whatever a function returns; Nest calls it once with the providers listed in inject.
  • useExisting gives a second token to a provider that already exists, so both names reach one instance.
Recipe What Nest injects What Nest builds Typical use
useValue the value you wrote nothing settings, constants, a fake in a test
useClass an instance of the class the class, once an implementation picked per environment
useFactory what the function returns calls the function, once objects built from other providers, connections
useExisting the other token’s instance nothing new an old name kept for a provider

Here is one module with all four. Step through how Nest reads its entries, then run it:

recipes/src/app.module.ts TypeScript · recipes/src/app.module.ts
import { Module } from '@nestjs/common';
import { LunchJob } from './lunch-job.js';
import {
  CUT_OFF_REMINDER,
  CutOffReminder,
  FakeSmsGateway,
  LiveSmsGateway,
  NOTIFIER,
  SMS_CONFIG,
  SMS_GATEWAY,
  type SmsConfig,
  type SmsGateway,
} from './sms.js';

@Module({
  providers: [
    // useValue: this exact object.
    { provide: SMS_CONFIG, useValue: { senderId: 'TFNTRL', cutOff: '10:30' } },
    // useClass: Nest builds this class; the condition picks it once, when this file loads.
    { provide: SMS_GATEWAY, useClass: process.env.SMS_MODE === 'live' ? LiveSmsGateway : FakeSmsGateway },
    // useFactory: whatever the function returns, called with the providers of inject, in that order.
    {
      provide: CUT_OFF_REMINDER,
      useFactory: (config: SmsConfig, gateway: SmsGateway) => new CutOffReminder(config, gateway),
      inject: [SMS_CONFIG, SMS_GATEWAY],
    },
    // useExisting: a second token for the provider that SMS_GATEWAY names.
    { provide: NOTIFIER, useExisting: SMS_GATEWAY },
    LunchJob,
  ],
})
export class AppModule {}
Interactive · step by step Four tokens, four recipes, one module Step through the providers of AppModule. Each step reads one entry, a token on the left, and shows what Nest will inject for it on the right.
Four tokens, four recipes, one module, step 5 of 5. Showing SMS_CONFIG, { senderId, cutOff }, SMS_GATEWAY, FakeSmsGateway (also NOTIFIER: one object), CUT_OFF_REMINDER, CutOffReminder, NOTIFIER, LunchJob. In focus: LunchJob.useValueuseClassuseFactoryinject 1stinject 2nduseExistinginjectedSMS_CONFIGa symbol token{ senderId, cutOff }the object as writtenSMS_GATEWAYa symbol tokenFakeSmsGatewayalso NOTIFIER: one objectCUT_OFF_REMINDERa symbol tokenCutOffReminderwhat the factory returnedNOTIFIERa symbol tokenLunchJob@Inject(CUT_OFF_REMINDER)remind(cus-41)

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. useValue. The first entry gives the token SMS_CONFIG an object, written in the module. Nest builds nothing: every class that asks for SMS_CONFIG receives this exact object.
  2. useClass. SMS_GATEWAY names a class. With SMS_MODE unset, the condition in the module picked FakeSmsGateway, and Nest builds it once, as it builds any provider class.
  3. useFactory. CUT_OFF_REMINDER is whatever the factory returns. Nest calls it once, passing the providers listed in inject, in that order, SMS_CONFIG first and SMS_GATEWAY second.
  4. useExisting. NOTIFIER is an alias. Nest does not build a second gateway; asking for NOTIFIER returns the instance that SMS_GATEWAY already names, and the run printed true for that.
  5. A consumer. LunchJob asks for the reminder with @Inject(CUT_OFF_REMINDER). A symbol is not a type the compiler can record, so the decorator names the token.

Four recipes, with and without SMS_MODE=live JavaScript · recipes/run.mjs
import { session } from '../session.mjs';

// The four recipes of one module, run twice: with the default gateway, then with SMS_MODE=live.
session(`
npx tsc -p tsconfig.json
node dist/main.js
SMS_MODE=live node dist/main.js
`);

Output

$ npx tsc -p tsconfig.json
$ node dist/main.js
SMS_MODE=(not set)
  SMS_CONFIG        {"senderId":"TFNTRL","cutOff":"10:30"}
  SMS_GATEWAY       an instance of FakeSmsGateway
  LunchJob.run()    kept in memory (1 so far); kept in memory (2 so far)
  NOTIFIER          the same object as SMS_GATEWAY: true
$ SMS_MODE=live node dist/main.js
SMS_MODE=live
  SMS_CONFIG        {"senderId":"TFNTRL","cutOff":"10:30"}
  SMS_GATEWAY       an instance of LiveSmsGateway
  LunchJob.run()    would send 36 characters to cus-41; would send 36 characters to cus-42
  NOTIFIER          the same object as SMS_GATEWAY: true

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node run.mjs

The two runs differ only where the gateway does: the condition in useClass chose FakeSmsGateway when SMS_MODE was unset and LiveSmsGateway with SMS_MODE=live, so the reminders of LunchJob went through another gateway while the module and every consumer stayed the same. In both runs NOTIFIER was the same object as SMS_GATEWAY, because an alias provider: A custom provider with useExisting: a second token for a provider that already exists, so both tokens reach the same instance. reuses an instance instead of building one.

Checkpoint

Check: The four provider recipes

  1. Question 1 of 2 Which recipe fits the SMS settings, the plain object { senderId: "TFNTRL", cutOff: "10:30" }?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: useValue

    The recorded run printed the object back unchanged: SMS_CONFIG {"senderId":"TFNTRL","cutOff":"10:30"}. Values, constants and fakes in tests are what useValue is for.

  2. Question 2 of 2 A factory is declared (config, gateway) => new CutOffReminder(config, gateway), with inject: [SMS_GATEWAY, SMS_CONFIG]. What does its config parameter receive?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The gateway, because Nest passes the providers of inject in the order they are listed

    In the lesson's module inject is [SMS_CONFIG, SMS_GATEWAY], matching the parameters. Swap the list and the factory silently receives the two values the other way round.

Symbols, strings and classes as tokens

A class token needs no help: the compiler records the parameter’s type and Nest looks that class up. Custom providers often use tokens that are not classes, and then the recorded type cannot help: an interface such as SmsGateway is recorded as Object. Such a parameter names its token with @Inject():

recipes/src/lunch-job.ts TypeScript · recipes/src/lunch-job.ts
import { Inject, Injectable } from '@nestjs/common';
import { CUT_OFF_REMINDER, type CutOffReminder } from './sms.js';

/** A consumer: a token that is not a class needs @Inject(token) on the parameter. */
@Injectable()
export class LunchJob {
  constructor(@Inject(CUT_OFF_REMINDER) private readonly reminder: CutOffReminder) {}
  run(customers: string[]) {
    return customers.map((c) => this.reminder.remind(c));
  }
}

Strings and symbols both work as tokens, and they differ in one way that matters. A symbol token: An injection token made with Symbol(). Every Symbol() call creates a new value, so two modules cannot register the same symbol token by accident, unlike two strings with the same text. made with Symbol() is unique: two modules that each create Symbol('CONFIG') hold two different tokens. Two string tokens with the same text are one token, so when two imported modules both export a 'CONFIG', the importer gets one of them, without an error. Define each token once, as an exported symbol in a file of its own, and import it wherever it is provided or injected.

Token How a consumer asks for it Can two modules clash?
a class, such as WalletService by its type alone no: two classes are two tokens
an abstract class, such as WalletRepository by its type alone no
a string, such as 'CONFIG' @Inject('CONFIG') yes: the same text is the same token
a symbol, such as SMS_GATEWAY @Inject(SMS_GATEWAY) no: every Symbol() call is a new token

An interface as the type, and no @Inject()

constructor(private readonly gateway: SmsGateway) compiles, and Nest refuses to start, because the compiler recorded Object for the interface. Add @Inject(SMS_GATEWAY) to the parameter: the type stays for the compiler, and the decorator gives Nest the token.

Checkpoint

Check: Tokens that are not classes

  1. Question 1 of 2 Suppose LunchJob imported CutOffReminder as a value, without the type keyword, and its parameter typed CutOffReminder had no @Inject(CUT_OFF_REMINDER). What would Nest look for?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The class CutOffReminder as a token, which no entry provides, so Nest cannot resolve it

    A token is what an entry provides. The reminder is provided as CUT_OFF_REMINDER, so the parameter must name that token with @Inject(), whatever its type says. With the type-only import of the lesson's lunch-job.ts the recorded type would be Function instead, and the start-up error would name Function, as the providers lesson showed.

  2. Question 2 of 2 Two modules each create Symbol('CONFIG') and export it. A third module imports both. Do the two tokens collide?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: No; every Symbol() call creates a token of its own, whatever its description

    Strings with the same text are one token, symbols are not. That is why an exported symbol per token, imported where it is used, cannot collide with another module's.

Factories that wait, fall back and fail fast Pro: opens with a Pro, Premium or Ultimate pass

A factory may return a promise, and Nest waits for it before it builds anything that needs the result. The program below runs three start-ups: a connection that answers, one that never does, and the same one with a deadline.

In this part:

  • An async factory holds start-up until it resolves, step by step
  • Optional dependencies of a factory resolve to undefined
  • A connection that never answers: the hang reproduced, and a deadline that fixes it
  • Where to open a connection: factory, hook or constructor, compared

2 runnable examples (JavaScript, TypeScript) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Token collisions, aliases and test overrides, with two version traps reproduced Pro: opens with a Pro, Premium or Ultimate pass

Three things look interchangeable and are not: two string tokens with the same text, an alias and a second class provider, and an override in a test on two Nest versions. Each one is run here, and the output decides.

In this part:

  • Two modules, one string token: the silent collision reproduced
  • useExisting shares an instance; a second useClass builds another
  • Two version traps between 12.0.4 and 12.1.2, run on both

4 runnable examples (JavaScript, TypeScript) · about 5 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise: resolve the four recipes

Write the small container that the first section described: values, classes, factories and aliases, each built at most once. Its sample tests run in your browser and use the lesson’s own tokens:

Exercise · Medium · JavaScript

Resolve the four provider recipes

Teach a small container Nest's four recipes. createContainer(entries) receives the entries of a module's providers, each with a token (provide) and one recipe:

  • { provide, useValue }: the token is that exact value;
  • { provide, useClass, deps }: an instance of the class, built with new useClass(...) and the instances of the tokens in deps, in that order;
  • { provide, useFactory, inject }: whatever useFactory(...) returns, called with the instances of the tokens in inject, in that order;
  • { provide, useExisting }: the same instance as the token it names.

Return { get }. get(token) returns the token's instance and builds it on first use; every later get and every entry that needs the token receive that same instance, so each class is built once and each factory is called once. Entries may need tokens listed after them. For a token that no entry provides, throw an Error with the message no provider for <token>, the token converted with String(): a symbol made by Symbol('SMS_GATEWAY') is written Symbol(SMS_GATEWAY).

Starter code · recipes.mjs

/**
 * A container for Nest's four recipes: entries { provide, useValue }, { provide, useClass, deps },
 * { provide, useFactory, inject } and { provide, useExisting }. Returns { get(token) }.
 */
export function createContainer(entries) {
  const byToken = new Map(entries.map((e) => [e.provide, e]));
  return {
    get(token) {
      const entry = byToken.get(token);
      // Handle each recipe, build each token once, and fail for a token without an entry.
      return entry?.useValue;
    },
  };
}
The sample tests · recipes.test.mjs
import { assert, test } from 'mysmartcopilot:test';
import { createContainer } from './recipes.mjs';

const SMS_CONFIG = Symbol('SMS_CONFIG');
const SMS_GATEWAY = Symbol('SMS_GATEWAY');
const CUT_OFF_REMINDER = Symbol('CUT_OFF_REMINDER');
const NOTIFIER = Symbol('NOTIFIER');

class FakeSmsGateway {
  static made = 0;
  constructor() {
    FakeSmsGateway.made += 1;
  }
}

/** The lesson's module: the four recipes, the alias listed before the token it names. */
function lessonModule() {
  FakeSmsGateway.made = 0;
  const calls = [];
  const config = { senderId: 'TFNTRL', cutOff: '10:30' };
  const container = createContainer([
    { provide: NOTIFIER, useExisting: SMS_GATEWAY },
    { provide: SMS_CONFIG, useValue: config },
    { provide: SMS_GATEWAY, useClass: FakeSmsGateway, deps: [] },
    { provide: CUT_OFF_REMINDER, useFactory: (c, g) => (calls.push([c, g]), { config: c, gateway: g }), inject: [SMS_CONFIG, SMS_GATEWAY] },
  ]);
  return { container, calls, config };
}

test('useValue gives the exact object, not a copy', () => {
  const { container, config } = lessonModule();
  assert.equal(container.get(SMS_CONFIG), config);
});

test('useClass builds the class once, however often it is asked for', () => {
  const { container } = lessonModule();
  assert.ok(container.get(SMS_GATEWAY) instanceof FakeSmsGateway);
  assert.equal(container.get(SMS_GATEWAY), container.get(SMS_GATEWAY));
  assert.equal(FakeSmsGateway.made, 1);
});

test('useFactory receives the instances of inject, in order, and is called once', () => {
  const { container, calls, config } = lessonModule();
  const reminder = container.get(CUT_OFF_REMINDER);
  container.get(CUT_OFF_REMINDER);
  assert.equal(calls.length, 1);
  assert.equal(reminder.config, config);
  assert.equal(reminder.gateway, container.get(SMS_GATEWAY));
});

test('useExisting returns the same instance as the token it names, even when listed first', () => {
  const { container } = lessonModule();
  assert.equal(container.get(NOTIFIER), container.get(SMS_GATEWAY));
  assert.equal(FakeSmsGateway.made, 1);
});

test('useClass passes the instances of deps to the constructor', () => {
  class Reminder {
    constructor(config, gateway) {
      this.config = config;
      this.gateway = gateway;
    }
  }
  const container = createContainer([
    { provide: 'Reminder', useClass: Reminder, deps: ['Config', 'Gateway'] },
    { provide: 'Config', useValue: { cutOff: '10:30' } },
    { provide: 'Gateway', useClass: FakeSmsGateway, deps: [] },
  ]);
  const r = container.get('Reminder');
  assert.deepEqual(r.config, { cutOff: '10:30' });
  assert.equal(r.gateway, container.get('Gateway'));
});

test('a value of 0 or false is still a provided value', () => {
  const container = createContainer([{ provide: 'RETRIES', useValue: 0 }, { provide: 'LIVE', useValue: false }]);
  assert.equal(container.get('RETRIES'), 0);
  assert.equal(container.get('LIVE'), false);
});

test('a token without an entry throws, a symbol written as String() writes it', () => {
  const container = createContainer([{ provide: NOTIFIER, useExisting: SMS_GATEWAY }]);
  assert.throws(() => container.get('Kitchen'), 'no provider for Kitchen', 'get should throw this exact message');
  assert.throws(() => container.get(NOTIFIER), 'no provider for Symbol(SMS_GATEWAY)', 'an alias to a missing token fails with the missing token');
});
A hint

Index the entries by token in a Map, and keep a second Map for the instances already made. Write one function, make(token), that returns the stored instance when there is one, and otherwise looks at which of the four keys the entry has: return the value, call make on each of deps or inject before calling the class or the factory, or return make(useExisting) for an alias. Store the result before returning it.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then the questions on the recipes and the tokens:

Practice that adapts to you

Practise this lesson

8 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 8 Which recipe makes Nest call a function once and inject what it returns?

    Choose one answer.

    Show the answer: Question 1 of 8

    Answer: useFactory

    The reminder of the lesson is a factory provider: (config, gateway) => new CutOffReminder(config, gateway), called once with SMS_CONFIG and SMS_GATEWAY.

  2. Question 2 of 8 What does the bare entry providers: [WalletService] stand for?

    Choose one answer.

    Show the answer: Question 2 of 8

    Answer: { provide: WalletService, useClass: WalletService }

    Every entry is a token and a recipe; the bare class form is the common case written short.

  3. Question 3 of 8 With SMS_MODE=live, SMS_GATEWAY resolved to LiveSmsGateway. What had to change in LunchJob or in the reminder factory?

    Choose one answer.

    Show the answer: Question 3 of 8

    Answer: Nothing; they ask for tokens, and only the recipe of SMS_GATEWAY changed

    The two recorded runs used the same module and the same consumers; only the line "SMS_GATEWAY an instance of …" and the reminder's result changed.

  4. Question 4 of 8 Old code asks for NOTIFIER and new code for SMS_GATEWAY, and both must see the same list of sent messages. Which entry should NOTIFIER have?

    Choose one answer.

    Show the answer: Question 4 of 8

    Answer: { provide: NOTIFIER, useExisting: SMS_GATEWAY }

    The run printed "NOTIFIER the same object as SMS_GATEWAY: true" for the useExisting entry.

  5. Question 5 of 8 Which constructor parameter needs @Inject() to be resolved?

    Choose one answer.

    Show the answer: Question 5 of 8

    Answer: One whose provider is registered under a symbol or a string

    @Inject(TOKEN) tells Nest which token to look up. A class token is found from the recorded type without it.

  6. Question 6 of 8 A parameter is typed SmsGateway, an interface, and has no @Inject(). What happens?

    Choose one answer.

    Show the answer: Question 6 of 8

    Answer: Nest refuses to start, because the compiler recorded Object for the interface

    Keep the interface as the type and add @Inject(SMS_GATEWAY): the compiler checks the methods and Nest gets the token.

  7. Question 7 of 8 Where should a symbol token such as SMS_GATEWAY be created?

    Choose one answer.

    Show the answer: Question 7 of 8

    Answer: Once, exported from a file of its own, and imported wherever it is provided or injected

    The lesson's tokens live in sms.ts and are imported by app.module.ts and lunch-job.ts.

  8. Question 8 of 8 Two teams' modules both export a provider with the string token 'CONFIG', and your module imports both. What is the risk?

    Choose one answer.

    Show the answer: Question 8 of 8

    Answer: Your classes receive one team's CONFIG and nothing reports the conflict

    Give each module its own exported symbol and the conflict cannot happen.

Interview questions

  1. Fresher to mid · Warm-up

    Name the four recipes of a custom provider in NestJS, and say what each one injects.

    A custom provider is an entry { provide: token, ... } with one recipe. useValue injects an object exactly as written, such as settings or a test double; Nest builds nothing. useClass makes Nest build a class for the token, which lets a module pick an implementation per environment while consumers keep asking for the same token. useFactory injects whatever a function returns: Nest calls it once and passes the providers listed in inject, in that order. useExisting gives a second token to a provider that already exists, so both tokens reach one instance. A token that is not a class, such as a symbol, is injected with @Inject(token) on the parameter. In this lesson's module the four recipes gave SMS_CONFIG, SMS_GATEWAY, CUT_OFF_REMINDER and NOTIFIER, and NOTIFIER was the same object as SMS_GATEWAY.

  2. Mid to senior

    Your service sends SMS through a gateway that must be fake in tests, a provider's sandbox in development and the live API in production, with a metrics client that only production has. How would you wire that in Nest so that no consumer knows which one it got?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    After a network change, a Nest service never finishes starting: no error, no log line after the database client begins to connect, and the platform restarts it every few minutes. The client is created by an async factory provider. Explain what is happening and how you would make the failure visible.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    An integration test replaces a configuration provider with overrideProvider() and a factory, and it passes, yet a colleague finds that it reads the production rate table. How would you work out why, and how would you keep tests that rely on overrides honest from now on?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A provider entry pairs a token with one recipe: useValue for an existing object, useClass for a class Nest builds, useFactory for what a function returns, and useExisting for a second name of an existing provider.
  • A factory receives the providers listed in inject, in that order, and useClass can pick an implementation per environment without changing any consumer.
  • A token that is not a class, such as a symbol for an interface, is injected with @Inject(TOKEN).
  • Prefer exported symbols to strings: two string tokens with the same text are one token, and an importer gets one of them without an error.

Terms in this lesson

alias provider
A custom provider with useExisting: a second token for a provider that already exists, so both tokens reach the same instance.
custom provider
A providers entry written out as { provide: token, … } with one recipe, useValue, useClass, useFactory or useExisting, instead of a bare class. It lets a token stand for a value, a chosen class or what a function returns.
factory provider
A custom provider with useFactory: its value is what a function returns. Nest calls the function once, passing the providers listed in inject in that order.
symbol token
An injection token made with Symbol(). Every Symbol() call creates a new value, so two modules cannot register the same symbol token by accident, unlike two strings with the same text.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.